Apr 29 2010

NHibernate soft-deletes: moving an entity to another table

Category: Uncategorizedzvolkov @ 4:11 pm

Here’s my scenario: I have two tables, Transactions and DeletedTransactions with identical schema. I need to delete a Transaction from the first table, and save it to the second. (I know I could do it with triggers, but what fun would that be?)

The simplest solution would be to create two classes, map each one to its corresponding table, then simply copy all fields from one object to another. A more elegant solution would be to use the entity-name approach, to override the mechanism by which NH decides which mapping to use for this particular operation. Basically, the idea is to map Transaction class to both tables by creating two mappings with different values of the optional entity-name attribute (which normally defaults to full class name). Unfortunately, as explained in the article, this approach has one serious drawback: every single place where you retrieve or persist the entities has to be modified to explicitly specify the entity name. This means all Save, Get, and CreateCriteria operations for both Transactions and DeletedTransactions have to be modified!

Fortunately by a clever use of polymorphism we can trick NH into using Transactions mapping by default, and only using DeletedTransactions when explicitly specified. This way the only places you have to change are those where DeletedTransactions are created. Finally, we can encapsulate creation of DeletedTransactions into a listener class using some nice NH Event Listener magic. Here are the step-by-step instructions:

1. Define your DeletedTransaction class as normally:

public class DeletedTransaction
{
	public virtual long Id { get; set; }
	public virtual DateTime TradeDate { get; set; }
	//other properties
}

2. Derive your Transaction from DeletedTransaction, this will allow casting any Transaction to DeletedTransaction:

public class Transaction : DeletedTransaction
{
	//no properties defined here as the two classes are identical
}

3. Create normal Transactions mapping (don’t override entity-name):

<class name="Transaction" table="Transactions">
    <id name="Id" column="TransactionID">
      <generator class="identity"/>
    </id>
	<!-- properties etc. -->
</class>

4. Create normal DeletedTransactions mapping (don’t override entity-name either). Remember to use generator class=assigned for the identity column if you want to keep the identity value:

<class name="DeletedTransaction" table="DeletedTransactions">
    <id name="Id" column="TransactionID">
      <generator class="assigned" />
    </id>
	<!-- properties etc. -->
</class>

5. Create PreDelete event listener that will create DeletedTransaction whenever Transaction is deleted:

public class HandleDeletedTransactions : IPreDeleteEventListener
    {
        public bool OnPreDelete(PreDeleteEvent deleted)
        {
            if (deleted.Entity is Transaction)
            {
                var tran = deleted.Entity as Transaction;
                var session = deleted.Session.GetSession(EntityMode.Poco);
                tran.DeletedDate = DateTime.Now;
                tran.DeletedByUserID = 1973;

                //insert into DeletedTransactions
                //(NH uses object's type name to figure out which mapping to use
                //this will tell NH to treat this Transaction as DeletedTransaction instead):
                session.Save(typeof(DeletedTransaction).FullName, tran);

                session.Flush();
            }
            return false;
        }
    }

6. Register the listener with NH configuration (here I am showing how to do it with the code, you can do it with XML config as well)

configuration.SetListener(ListenerType.PreDelete, new HandleDeletedTransactions());

No other code needs to be modified. Indeed, all the Get and CreateCriteria explicitely specify the class, and the Save and Update of Transactions get the class from GetType() which will obviously return “Transaction” and not its base “DeletedTransaction”!

Whenever it feels just a little bit dirty but does the trick I say: “Nice hack!”


Apr 16 2010

Best Practice Rejection Sequence (BPRS)

Category: Uncategorizedzvolkov @ 11:53 am

Following the best practices is all fine and dandy. But what happens under pressure, when deadline is approaching, PROD is down, and CIO is breathing down your neck? In most companies, all the practices are simply dropped and people just do whatever “fastest” thing they can. “Fastest” usually means such that feels simpler, i.e. requires less emotional energy. This approach usually leads to adoption of quick-and-dirty solutions commonly known as The Ball Of Mud that start as temporary and end up being permanent .

Instead, I propose to bring some order into this process and establish a sequence in which the best practices should be surrendered. While you can argue what specific items and their relative priorities would work better in your team, it is my strong opinion that the concept of such list is universally applicable.

Here’s my first take of such a list, starting with the items I would drop first (documentation), and finishing with the ultimate rejection (not developing anything altogether). I would be really interested to see what your list would look like, please reply in the comments.

  1. Design Documentation
  2. Performance micro-optimizations (at the code level)
  3. Following official company-prescribed coding standards
  4. Ensuring the solution will work with Continuous Integration (unit-tests running on check-in)
  5. Adherence to low-level design standards (S.O.L.I.D. principles of OOD)
  6. Sufficient research of design/implementation approaches (e.g. on the internet) done before coding
  7. Neat code formatting
  8. Adherence to basic coding standards (small classes, small methods, no copy/paste etc.)
  9. Discussing solution with other team members
  10. Ensuring solution will work with Automated Builds process
  11. Adding or updating unit-tests
  12. Ensuring the solution won’t incur extra maintenance overhead
  13. Ensuring the solution will not negatively affect other systems it has no direct relationship to (e.g. overall system performance etc.)
  14. Manual testing done before pushing to PROD
  15. Whether the solution should be developed altogether

This list works at a subtler level than its simple structure would suggest. It changes the holy war attitude, the black and white attitude, of “professionals always do documentation”, “agilists could care less about documentation, but they always do unit-tests” etc. to a much more workable prioritized sequence. Also, by contrasting practices well-known in management circles (such as coding standards) with those well-known by junior programmers (performance) and agile programmers (unit-tests) the practices are put into context of relative benefit/harm their adoption/rejection does to the big picture.

I strongly encourage you to create such list, thoroughly discuss it with your team, make sure everybody understands each item and has a chance to argue about its place in the sequence. You may even want to hang the list on the cubicle walls, which is what I plan to do once I finalize it with the team.


Apr 08 2010

Team City basics for MSBuild and SVN

Category: Uncategorizedzvolkov @ 12:24 pm

Basic build process

  1. TeamCity retrieves sources from SVN to Build Check Out Directory. By default, TeamCity optimizes communication with SVN servers by retrieving only the necessary changes.  For more info on this read http://confluence.jetbrains.net/display/TCD5/VCS+Checkout+Mode and http://confluence.jetbrains.net/display/TCD5/Clean+Checkout
  2. TeamCity generates .proj file in the Build Temp Directory and executes MSBuild. This is where you can see every single parameter passed by TeamCity to MSBuild! The generated .proj file references the actual build script located in the Build Check Out Directory
  3. MSBuild executes actual build script. Current directory is set to Build Working Directory (which by default is same as the checkout dir), so this is what all relative paths start from.
  4. The script should output the binaries to a subfolder of Build Check Out Directory
  5. Artifacts produced by the build are picked up and uploaded to Team City Data Directory and made available for download via the Web UI. For more info see http://confluence.jetbrains.net/display/TCD5/Build+Artifact

Directories

  • Build Check Out Directory
    • Significance: contains source code for particular project. The binaries produced by the build should also go here.
    • Default: C:\BuildAgent\work\[hash code] — The hash code is calculated based on SVN settings used by the build configuration. Effectively, this means that the directory is shared between all the build configurations with the same settings.
    • Property: $(teamcity_build_checkoutDir)
    • Documentation: http://confluence.jetbrains.net/display/TCD5/Build+Checkout+Directory
  • Build Temp Directory
    • Significance: stores MSBuild proj file and parameters generated by TeamCity; can be used to store other temp stuff; cleaned-up before every build.
    • Default: C:\BuildAgent\temp\buildTmp
    • Property: $(teamcity_build_tempDir)

If you want to give a direct link to a build artifact to somebody (e.g. for QA or deployment), you can use the following syntax:

http://<SERVER>/repository/download/<PROJECT>::<CONFIGURATION>/<BUILD_NUMBER>/<ARTIFACT_NAME>

For example:

http://rocktechtfsrep.tech.corp.local:81/repository/download/CDI 2.0::CDI Trunk/2.5.0.10/CDI2_TEMP.msi

Read this for more details: http://confluence.jetbrains.net/display/TCD5/Patterns+For+Accessing+Build+Artifacts