Skip to content

Detailed Design

ulisespulido edited this page Nov 10, 2011 · 3 revisions

Expanding on the Basic Design with implementation-specific details, this section describes the current implementation of AutoPatch, with references to actual classnames.

Startup

The MigrationLauncher object is the main entry point of the AutoPatch system. There are a few adaptors that have been implemented, one for web application integration (WebAppMigrationLauncher), one for command-line integration (StandaloneMigrationLauncher), as well as a few for depencency injection containers (AutoPatchService). There is also a "distributed" version of each launcher that allows you to configure multiple sub-launchers together as one large system orchestrated as a single unit with a single patch level.

The sole purpose of the adaptors is to instantiate the MigrationLauncher object, and call the "doMigrations" method.

Once the StandaloneMigrationLauncher is executing, it will read a configuration file from the classpath called migration.properties. An example migration.properties is provided in the same directory as this README. The configuration file is responsible for configuring the database connection for the AutoPatch system, as well as for specifying where in the classpath AutoPatch should search for patches.

Other MigrationLaunchers are configured in different ways, and you should consult the javadoc information in the adapter that you wish to use.

Patch Level

Once the MigrationLauncher has started executing, and finished reading in its configuration file, it will use the database parameters to configure the PatchTable object. PatchTable will inspect the database and attempt to get the current patch level of the system. If no patch table is present, it will create one (named 'patches').

Currently, the configuration file specifies a "database type", and the name of the type specifies which SQL property file to load, so PatchTable can interact with multiple database types. For example, if the type is "postgres", PatchTable will attempt to load the SQL property file "postgres.properties" and use the SQL in that file to interact with the database when persisting patch level information.

Patch Loading

Once the patch level is determined, the colon-separated patch path specified in the configuration file is searched for all objects that implement the MigrationTask type. Each directory in your classpath has each part of the patch-path appended to it, and each of these combinations is searched for patches.

There are a couple of MigrationTask sub-classes that are useful to extend, and in practice all patches use the sub-classes.

The first sub-class is the SqlScriptMigrationTask. It allows for raw text files containing SQL to be used as migrations, so long as they follow the naming convention 'patchNNNN_{name}.sql' (e.g. 'patch0001_create_initial_schema.sql'), where the numbers are globally unique among all patches.

The second sub-class is the SqlLoadMigrationTask, which allows you to quickly implement domain data loads with the use of tab-delimited text files and a sub-class that understands how to insert one row of the data at a time.

Another data load type is the FlatXmlDataSetTaskSource. This class allows you to use xml data files to bulk load data into the system. As per the name, the xml format is the same as the DBUnit FlatXmlDataSet format type. Data sets in this format must match the typical patch naming standard, except that it must end with a .xml suffix (patchNNNN_{name}.xml). Examples:

patch0002_initial_data.xml
patch0005_load_stuff.xml

Another sub-class currently implemented is MigrationTaskSupport. This is a more basic patch object, and allows for any arbitrary logic to be implemented in Java and executed under the guise of a patch. It is potentially very useful to perform a data migration after one patch adds new tables and columns, but before another patch removes other tables or columns.

Patch Ordering

Its important to realize that patches must have a total global ordering - meaning that each patch must be associated with a unique level.

Java patches get the opportunity to specify their own level, but for the sql and xml patches, ordering is based on the naming convention specified above. This may seem cavalier at first, but its simple and easy to do in practice so long as you check the current patch level before creating a new patch, and coordinate with your fellow patch authors if there are multiple developers working on a system.

To determine the current patch level easily, its possible to execute the MigrationInformation object as a standalone Java application, which will tell you the current patch level in the database, and the current maximum patch level implemented by MigrationTasks in the classpath. A suggested ant target to implement this check looks like this:

<target name="patch.information" description="Gets patch information from code and the database">
  <java
      fork="true"
      classpathref="inttest.classpath"
      failonerror="true"
      classname="com.tacitknowledge.util.migration.jdbc.MigrationInformation">
    <sysproperty key="migration.systemname" value="${application.name}"/>
  </java>
</target>

The classpath must contain the migration.properties, the AutoPatch jar, as well as all of the patch objects, for the information to be accurate.

For distributed systems, it is important to note that you may not have two patches with the same patch number in multiple sub-launchers. Each patch must have a globally unique patch level across all sub-launchers.

Patch Execution

Patch execution is relatively straightforward, except for the possibility that application servers in a cluster may all start at the same time, and attempt to execute patches at the same time.

In order to ensure that each patch only executes once, the PatchTable object has logic that locks the patch state table ('patches'), guaranteeing that multiple servers in a cluster will serialize through the AutoPatch part of their startup, and thus insuring that the set of patches is only executed once.

Other than that, each patch is executed once, with the system patch level incremented for each successful patch application. Any unsuccessful patch application will immediately result in voluminous logging and fatal errors propoaged to the adapter that started AutoPatch, as this is a very serious problem and would prevent the correct execution of the application, and urgent troubleshooting will usually be required.

Note that it is possible, by setting the "ReadOnly" property, to get AutoPatch to go through each complete patch phase except for the actual application, so that you can see what would happen if you applied patches. If the level isn't correct it will throw an exception when it is read-only. So you can enforce that the patch level is correct, but not automatically patch, useful in production configurations.

Migration Strategy

A developer can choose to use the default migration strategy which is ordered by leaving empty or not setting the migration.strategy property in the migration.properties file. There is another implementation that supports applying missing patches useful when you use multiple branches in a source file control.

The developer can also create their own strategy by implementing the methods from the MigrationRunnerStrategy interface and changing the migration.strategy property to use it by providing the full classname of the created strategy.