google-nomulus/docs/code-structure.md
shikhman 7794fb04d1 Add documentation on Bazel to the code structure docs
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=136083155
2016-10-14 17:00:32 -04:00

9.7 KiB

Code structure

This document contains information on the overall structure of the code, and how particularly important pieces of the system are implemented.

Dagger dependency injection

Bazel build system

Bazel is used to build and test the Nomulus codebase.

Bazel builds are described using BUILD files. A directory containing a BUILD file defines a package consisting of all files and directories underneath it, except those directories which themselves also contain BUILD files. A package contains targets. Most targets in the codebase are of the type java_library, which generates JAR files, or java_test, which runs tests.

The key to Bazel's ability to create reproducible builds is the requirement that each build target must declare its direct dependencies. Each of those dependencies is a target, which, in turn, must also declare its dependencies. This recursive description of a target's dependencies forms an acyclic graph that fully describes the targets which must be built in order to build any target in the graph.

A wrinkle in this system is managing external dependencies. Bazel was designed first and foremost to manage builds where all code lives in a single source repository and is compiled from HEAD. In order to mesh with other build and packaging schemes, such as libraries distributed as compiled JARs, Bazel supports external target declarations. The Nomulus codebase uses external targets pulled in from Maven Central, these are declared in java/google/registry/repositories.bzl. The dependencies of these external targets are not managed by Bazel; you must manually add all of the dependencies or use the [generate_workspace](https://github.com/bazelbuild/bazel/tree/master/src/tools/generate_workspace tool to do it.

How to generate EAR/WAR archives for deployment

There are special build target types for generating WAR and EAR files for deploying Nomulus to GAE. These targets, zip_file and registry_ear_file respectively, are used in java/google/registry/BUILD. To generate archives suitable for deployment on GAE:

$ bazel build java/google/registry:registry_ear
  ...
  bazel-genfiles/java/google/registry/registry.ear
INFO: Elapsed time: 0.216s, Critical Path: 0.00s
# This will also generate the per-module WAR files:
$ ls bazel-genfiles/java/google/registry/*.war
bazel-genfiles/java/google/registry/registry_backend.war
bazel-genfiles/java/google/registry/registry_default.war
bazel-genfiles/java/google/registry/registry_tools.war

Flows

Commit logs and backups

Cursors

Cursors are DateTime pointers used to ensure rolling transactional isolation of various reporting and other maintenance operations. Utilizing a Cursor within an operation ensures that instances in time are processed exactly once for a given task, and that tasks can catch up from any failure states at any time.

Cursors are rolled forward at the end of successful tasks, are not rolled forward in the case of failure, and can be manually set backwards using the nomulus update_cursors command to reprocess a past action.

The following cursor types are defined:

  • BRDA - BRDA (thin) escrow deposits
  • RDE_REPORT - XML RDE report uploads
  • RDE_STAGING - RDE (thick) escrow deposit staging
  • RDE_UPLOAD - RDE (thick) escrow deposit upload
  • RDE_UPLOAD_SFTP - Cursor that tracks the last time we talked to the escrow provider's SFTP server for a given TLD.
  • RECURRING_BILLING - Expansion of Recurring (renew) billing events into OneTime events.

All Cursor entities in Datastore contain a DateTime that represents the next timestamp at which an operation should resume processing and a CursorType that identifies which operation the cursor is associated with. In many cases, there are multiple cursors per operation; for instance, the cursors related to RDE reporting, staging, and upload are per-TLD cursors. To accomplish this, each Cursor also has a scope, a Key<ImmutableObject> to which the particular cursor applies (this can be e.g. a Registry or any other ImmutableObject in datastore, depending on the operation). If the Cursor applies to the entire registry environment, it is considered a global cursor and has a scope of EntityGroupRoot.getCrossTldKey().

Cursors are singleton entities by type and scope. The id for a Cursor is a deterministic string that consists of the websafe string of the Key of the scope object concatenated with the name of the name of the cursor type, separated by an underscore.

Mapreduces

Nomulus uses the App Engine MapReduce framework extensively, both for a variety of regularly scheduled background tasks and for one-off maintenance tasks. The MapReduce framework comes with a web UI for viewing the status of ongoing and completed tasks.

Most MapReduces in Nomulus work by mapping over all entities of a given set of Datastore kind(s) (e.g. domains, contacts, etc.). All of the MapReduces in Nomulus are run by the MapreduceRunner class, which provides a standard set of ways to set the number of mapper and reducer shards. It is common to run map-only MapReduces when reducers aren't needed; these are supported as well.

The main complication with MapReduces is that the mapper and reducer classes are required to be serializable as a consequence of how work is sharded out, pasued/resumed, and moved around. All fields on these classes must therefore be either Serializable or transient. This also means that dependency injection is of limited use -- the best you can do is to @Inject serializable fields on the entire MapReduce Action, and then set them manually on the mapper/reducer classes in their constructor.

Actions and servlets

Foreign key indexes

Point-in-time accuracy

Guava

The Nomulus codebase makes extensive use of the Guava libraries. These libraries provide idiomatic, well-tested, and performant add-ons to the JDK. There are several libraries in particular that you should familiarize yourself with, as they are used extensively throughout the codebase:

  • Immutable Collections: Immutable collections are a useful defensive programming technique. When an Immutable collection type is used as a parameter type, it immediately indicates that the given collection will not be modified in the method. Immutable collections are also more memory-efficient than their mutable counterparts, and are inherently thread-safe.

    Immutable collections are constructed one of three ways:

    • Using a Builder: used when the collection will be built iteratively in a loop.
    • With the of method: used when constructing the collection with a handful of elements. Most commonly used when creating collections representing constants, like lookup tables or whitelists.
    • With the copyOf method: used when constructing the method from a reference to another collection. Used to defensively copy a mutable collection (like a return value from an external library) to an immutable collection.
  • Optional: The Optional<T> class is used as a container for nullable values. It is most often used as return value, as an explicit indicator that the return value may be absent, thereby making a null return value an obvious error.

  • Preconditions: Preconditions are used defensively, in order to validate parameters and state upon entry to a method.

In addition to Guava, the codebase also extensively uses AutoValue value classes. AutoValue value type objects are immutable and have sane default implementations of toString, hashCode, and equals. They are often used as parameters and return values to encapsulate related values together.

EPP resource hierarchy

Poll messages

Poll messages are the mechanism by which EPP handles asynchronous communication between the registry and registrars. Refer to RFC 5730 Section 2.9.2.3 for their protocol specification.

Poll messages are stored by the system as entities in Datastore. All poll messages have an event time at which they become active; any poll request before that time will not return the poll message. For example, every domain when created enqueues a speculative poll message for the automatic renewal of the domain a year later. This poll message won't be delivered until that year elapses, and if some change to the domain occurs prior to that point, such as it being deleted, then the speculative poll message will be deleted and thus never delivered. Other poll messages are effective immediately, e.g. the poll message generated for the owning registrar when another registrar requests the transfer of a domain. These messages are written out with an event time of when they were created, and will thus be delivered whenever the registrar next polls for messages.

PollMessage is the abstract base class for the two different types of poll messages that extend it:

  • Autorenew - A poll message corresponding to an automatic renewal of a domain. It recurs annually.
  • OneTime - A one-time poll message used for everything else.

Queries for poll messages by the registrar are handled in PollRequestFlow, and poll messages are ACKed (and thus deleted) in PollAckFlow.

Security