zaterdag 31 mei 2008

DDAD: Domain Driven "Allors" Design

As explained in my previous post I am going to build an application to demonstrate what Allors is and how to use it.  I will hopefully have to time to build the same application later on using NHibernate to show both frameworks differences. 

The application I'll be building will take care of the invoicing for a contractor.  It is built using the YAGNI principle (this basically states if you think you need to add complexity to your domain model, you should defer it until the moment you actually need it.  Probably you ain't gonna).  Lets discuss the domain shall we.  The most important class obviously is the Contract signed between a Customer and a contractors's Company.  Both the customer and the company are subclasses of the corporation class, which gives them some common attributes (like Name, Address, Account, .... ).  On the Contract you can then register the hours you worked for them and eventually generate an Invoice for a specific period. 

All relations and classes are shown in the following model:

Version1 
I deliberately kept everything as simple as possible so I can make future improvements to demonstrate what this means in terms of database upgrades, refactorings possibilities, etc....  If you want to follow everything that is explained here below, you can download the complete solution here or built everything yourself of course.

Building the domain (Allors)

To start building an Allors domain you first need to download the binaries and the Allors Repository.  You can either checkout the sources from the subversion (https://www.allors.com/svn/platform) or you can download the Quick Start file I've put together. The zip contains two directories & a command file

  • lib: Directory containing the Allors binaries and the Allors Repository, you should extract these in your solution folder.
  • Allors: Directory for the Allors Domain Files, you should extract these in your domain project folder.
  • AllorsRepository.cmd: file that will startup the AllorsRepository Tool for you domain, this should be extracted in your solution folder.

Step 1: Setting up the solution hierarchy

The solution structure I have applied consists of 4 projects.

  • Diagrams: containing the diagrams of my domain.  This project will contain only generated code with getters/setter that reflect your domain.  You can then use these to generate your class diagrams.
  • Domain: the heart and soul of our accounting application, you should refer to the Allors.Framework.dll here.
  • Population: Helper classes to build your domain objects and setup an initial population for unit/integration testing.
  • Unittests: proving our design and domain is correct and well thought.

image 

Step 2: Creating the Allorized domain

The Allors folder from the zip file needs to be copied into your domain folder.  This folder will contain the meta information (allors.repository) about your domain classes, which is built up using the AllorsRepository-Application (included in the zip file).  if you modify the command file that was included in the zip file so that the parameter passed in to start up the "Allors.Repository.Application.exe" refers to the allors.repository in your domain folder, this file will now fire up the AllorsRepository-Application for your domain.  You should see the following (if you are building from scratch, otherwise your domain will be completely filled in).

image

This screen has three main parts. 

  • The top left has a tree containing all your types and relations in your domain
  • The top right is the property window of the selected treeNode
  • The bottom gives information about errors in your domain

All actions are available by rightclicking the treeNode and then select the action in the context menu or change the node's corresponding properties.  If you need to know how things are getting done, you can checkout the getting started page of the Allors website.  On the bottom you can see that the repository already has one error.  It has no name, so you need to select the domain node and fill in the name in the properties window.

Step 3: Namespaces & Types

Our solution will have two namespaces to start with.  You can see here how you can add namespaces in the repository.  The types we are building are shown in the diagram above, you can easily add them yourself in the allors repository.  It is written out here how you can achieve that.  All objects belong to the Accounting namespace, only the DatePeriod type is more generic and is placed into its own General namespaces

Step 4: Attributes & Relations

We now have domain containing single classes without any attributes or relationships between them.  You cannot call this a domain of course so the logical next step to give our objects data and connect them through the use of relations.  As always you can checkout here how everything can be done.  The above diagram gives you all the information you need to create all the relations.

Step 5: Adding Domain Logic

The Allors domain has been built (you can download the zip file here).  The next step is to generate (right click the domain and select generate) our allors classes, these will be our base classes for our domain objects.  You don't have to remember to inherit any classes or interfaces because we generate a partial class for you as well.  Include these files (located inside the Allors/output/folder by default, but this can be tweaked of course) in your domain project and you can start using them.  Every object you instantiate now is inside a Session, the creation is performed through the Session as you can see in the next code block:

1 public static Account Create(AllorsSession session, 2 string bank, 3 string number) 4 { 5 Check.Argument(number, "number").IsNotNullAndNotEmptyAndNotWhiteSpace(); 6 Check.Argument(bank, "bank").IsNotNullAndNotEmptyAndNotWhiteSpace(); 7 8 var account = session.Create<Account>(); 9 account.Bank = bank; 10 account.Number = number; 11 12 return account; 13 }

Adding the actual business logic is done simply by adding the methods into your class (as you normally would)

1 public TimeRegistration RegisterWorkingTime(DateTime date, Double hours) 2 { 3 return TimeRegistration.Create(AllorsSession, date, this, hours); 4 } 5 6 public Invoice BuildInvoice(DatePeriod period, 7 String reference, 8 DateTime invoiceDate, 9 IInvoiceCalculator invoiceCalculator) 10 { 11 VerifyThatInvoiceReferenceIsUnique(reference); 12 13 var timeRegistrations = new TimeRegistrationFinder(AllorsSession) 14 .GetNotInvoicedTimeRegistrationsFor(this,period); 15 if (timeRegistrations.Length == 0) 16 { 17 throw new ArgumentException("Invoice must contain TimeRegistrations"); 18 } 19 20 var invoice = Invoice.Create(AllorsSession, reference, invoiceDate); 21 AddInvoice(invoice); 22 23 foreach (var timeRegistration in timeRegistrations) 24 { 25 invoice.InvoiceTimeRegistration(timeRegistration, invoiceCalculator); 26 } 27 28 return invoice; 29 }

Step 6: Unit Testing

Since all objects are Session aware we need to have a Session in our UnitTests.  For performance reasons we will not use a real database, but rather work completely in memory.  The following code creates an in-memory session for our domain. 

var population = new Allors.Adapters.Memory.AllorsConnectedMemoryPopulation(new AccountingDomainConfiguration()); population.Init(); _session = population.CreateSession();

You can then create your objects with this sessions and perform the regular tests you would normally write.

1 [TestFixture] 2 public class When_creating_an_account : AllorsBaseTest 3 { 4 private AccountBuilder _builder; 5 6 [Test] 7 [ExpectedException(typeof(ArgumentNullException))] 8 public void Then_expect_an_error_when_the_number_is_null() 9 { 10 _builder = new AccountBuilder(Session); 11 _builder.WithBank(null).Build(); 12 } 13 }

The final step is to make our domain persistent.

For this I created a new project (IntegrationTests).  You do not need to supply mapping files because Allors will make and initialize the database scheme for you based on the meta information you supplied.  This is achieved by simply instantiating the correct population and Session, here we will use the SQL Server Express Edition.  For unittesting we created the population in code (and for memory), you could as well set everything in the config file:

  • First of all you need to declare the allors section in the config
  • You need to configure your populations(or multiple of them) + connectionstrings
  • Finally you need to create the population from based on the name in the config
1 <configSections> 2 <section name="allors" type="Allors.AllorsConfigurationSection, Allors.Framework"/> 3 </configSections> 4 5 <allors> 6 <populations> 7 <add name="AccountingPopulation" 8 type="Allors.Adapters.SqlClient.AllorsSqlClientPopulation, Allors.Adapters.SqlClient" 9 domainConfiguration="AllorsDomains.AccountingDomainConfiguration, Pdbc.Accounting.Domain" 10 connectionStringName="accounting"> 11 </add> 12 </populations> 13 </allors> 14 15 <connectionStrings> 16 <add name="accounting" connectionString="Data Source=Pdbc-Laptop\SqlExpress;Initial Catalog=accounting;Integrated Security=True"/> 17 </connectionStrings>

Then in code we can retrieve the population and instantiate the session.  The Init method by the way drops the database and recreates it which makes sure that our integration tests always have the latest version of the database scheme available.

1 var population = AllorsConfiguration.GetPopulation("AccountingPopulation"); 2 population.Init(); 3 _session = population.CreateSession();

Check the complete sample along with the unittests for a complete overview.  As you notice, the major difference with traditional POCO domain objects is that we need to inherit from an Allors Class.  This Class encapsulates the access to a strategy object which contains all your class attributes and object relations.  This way we can keep all objects managed by Allors which has a lot of benefits (lazy loading, managed relations, ...) , but that is for a next post. 

Let me know what you think about it and have a spin with the sample.  

dinsdag 27 mei 2008

Filtering your Nhibernate SQL Log: The good way

Apparently my previous post about filtering out the unique sql statements NHibernate fires at your database is a quick and dirty hack.  Sure it works but why create a complete new appender  if all you want to do is decorate the existing ones with filtering.  If I would have read the documentation that the good people of log4net have prepared for the world, I would have noticed the concept of Filters.  These form a chain that the logging event has to pass through in order to actually get logged.

Here is the same code that should do the trick using the filters, remember to add this filter in your appender declaration in the configuration file

public class NHibernateUniqueQueryFilter : FilterSkeleton { private static readonly List<String> statements = new List<string>(); public override FilterDecision Decide(LoggingEvent loggingEvent) { var message = loggingEvent.RenderedMessage; var index = loggingEvent.RenderedMessage.IndexOf(";"); if (index != -1) { message = loggingEvent.RenderedMessage.Substring(0, index); } if (statements.Contains(message)) { return FilterDecision.Deny; } statements.Add(message); return FilterDecision.Accept; } }

It's Peter Time

Today (actually yesterday) Peter Eysermans started his blog.  Peter is one of my colleagues and a really funny guy, he is an authority (or will be :-)) on Web Development.  So if you want to follow his quest for world web domination then check out his blog or subscribe to it).  I for one am looking forward to his next post : The pussy way of development'!

vrijdag 23 mei 2008

Resharper 4.0 Stable release

I am using Resharper since version 2.0 and VS2003.  In those days there were major problems with the memory consumption of the tool, resulting in a slow IDE and multiple crashes.  Just when I was going to remove it from my system, I made the shift to VS2005 and Resharper 2.5 (and 3.0).  It couldn't have come at a better time because I never doubted the guys from Jetbrains ever again.

A while ago I installed one of the 4.0 EAP Releases (4.0.775.22).  Some of my solutions not even the big ones took ages to start up and occasionally my studio froze!  I quickly reverted to my old version no harm done.  But a couple of days ago a Stable version was released and I decided to give it another try.  My problem solution now still take a little longer to load, but I didn't suffer any freezes anymore.  The support for the .NET 3.5 framework makes the loading time a necessary evil, besides you need this version if you really want to develop in VS2008 (and not be annoyed by non-compilation warnings from Resharper about the new language stuff)

You can buy a license for the currently released 3.5 and are allowed to upgrade to 4.0 once this version becomes available.  So what is stopping ya?

By the way, thanks to Ilya Ryzhenkov for providing a list of nice plug-ins for Resharper, these have proven to be quite helpful.

woensdag 21 mei 2008

Know what happens on your database

Although I believe that an ORM should take care of the persistence, you as a developer NEED to know what happens on your database.  You need to know what SQL - statements get fired.  NHibernate uses Log4Net for this, with a simple configuration setting you make NHibernate log all statements that make the trip to the database.  In the hibernate-configuration you can set the "show_sql" property to true.  You then need to specify the Nhibernate logger (be sure to name this "NHibernate.SQL" and link it to an appender.  The following config section simply does that:

1 <log4net> 2 <appender name="NHibernateSQLLog" type="log4net.Appender.RollingFileAppender"> 3 <file value="nhibernate_sql_statements.txt"/> 4 <appendToFile value="true"/> 5 <rollingStyle value="Size"/> 6 <maxSizeRollBackups value="10"/> 7 <maximumFileSize value="1000KB"/> 8 <staticLogFileName value="true"/> 9 <layout type="log4net.Layout.PatternLayout"> 10 <conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n"/> 11 </layout> 12 </appender> 13 <logger name="NHibernate.SQL" additivity="false"> 14 <level value="DEBUG"/> 15 <appender-ref ref="NHibernateSQLLog"/> 16 </logger> 17 </log4net>

As already mentioned in a previous post we had to be very persuasive in order to use NHibernate.  One of the questions our DBA guys had was "what was going on on their database, can we see what queries and statements get executed?".  The above gives us all information, however it is not very useful for them as they have to go through tons of duplicate statements.  Fortunately Log4Net is very extensible, you can easily build your Appender and filter out the information you do not want to log.  The easiest solution is to extend the appender you would normally use, (for example the RollingFileAppender) and override FilterEvent method.  You can then return false for every loggingEvent that you do not want written out. 

1 private static List<String> statements = new List<string(); 2 3 4 protected override bool FilterEvent(log4net.Core.LoggingEvent loggingEvent) 5 { 6 string message = loggingEvent.RenderedMessage; 7 int index = loggingEvent.RenderedMessage.IndexOf(";"); 8 if (index != -1) 9 { 10 message = loggingEvent.RenderedMessage.Substring(0, index); 11 } 12 13 if (statements.Contains(message)) 14 { 15 return false; 16 } 17 18 statements.Add(message); 19 20 return base.FilterEvent(loggingEvent); 21 }

In this example I build a list of all statements that have already been logged.  First we need to filter out the parameters, because otherwise no statement would be unique.  The loggingEvent contains a RenderedMessage that consist of two parts.  First the actual statement containing wildcards or named parameters.  Then the character ';' and lastly the mapping of the parameters.  We only need the part before the ';' because any statement can be called multiple times with different parameters.  Once we have the actual query statement we try to find this in our list and return false if it has already been logged.  The result is that our logfile now only contain unique statements.  The parameters included are those from the first time this statement was logged (Since the RenderedMessage is read-only we cannot remove these).

dinsdag 20 mei 2008

NHibernate's Optimistic Locking: Where did we go wrong?

I was trying to get optimistic locking to work properly by applying the following articles (this and this) from Ray Houston.  I got it up and running in a couple of minutes and my audit information was automatically saved whenever I inserted or updated an object.  If you think you can let NHibernate handle the version in this case you are wrong.  The version (or timestamp) attribute needs to map to a single database column if you want to use it for optimistic locking.

So in order to have a working locking strategy I removed the LastModified Timestamp from my CompositeUserType and added a specific timestamp property in my mapping file.  

Piece of cake, .... however  ..... what happens when you start working disconnected?  You have two possible solutions.  Keeping your Session open (which will kill your application's scalability) or you can re-attach the objects into a new session (prefer this solution if you can). 

Now consider my business use case where I am loading an entire object graph in my server session, these objects are transformed to DTO's and send to the client.  The client then makes some changes and sends me only the updated values.  In other words I cannot attach my objects into a new session because I simply do not have all the information at my disposal.  There is simple solution for this problem, do it yourself (check 10.4.4 application version checking of the Nhibernate online documentation). 

I decided to load the entire object graph again and set the LastModified whenever I apply the changes from the client.  My version check will happen in the interceptor in the OnFlushDirty method, there I will check the original value of the date with the version I got back from the client (and applied in the object).

Int32 lastModificationPropertyIndex = Array.IndexOf(propertyNames, lastModificationProperty); if (-1 != lastModificationPropertyIndex) { DateTime originalDateTime = (DateTime)(previousState[lastModificationPropertyIndex]); DateTime currentDateTime = domainObject.LastModification; if (originalDateTime.Ticks != currentDateTime.Ticks) { if(originalDateTime != DateTime.MinValue) { throw new StaleObjectException(id); } } }

The OnFlushDirty method is called only for dirty objects, the OnSave is called for new objects (although I added the check that the originalDateTime must be different than the MinValue if it should happen that this method gets called for new objects).  75% of the time this seems to work but sometimes the StaleObjectException is thrown when and object is not out-of-date.  The LastModified date is updated whenever the data is flushed to the database.  This flushing can be set on the session using the enumeration type FlushMode. Default is set to Automatic meaning that it can happen that the session is flushed before a query.  The session now gets flushed before the interceptor gets called (strange, but this seems to be the reason).  Setting the FlushMode to Commit or Never seems to resolve the above issue because then the flush has to be called manually or by committing a transaction.

Till next time

zaterdag 10 mei 2008

Allors vs NHibernate

Since we are now able to use NHibernate at my current job, I decided I needed to alter my blogging strategy a little bit.  It was my intention to demonstrate Allors in a couple of posts during which I would build up an invoicing application.  Since I will have to do some learning of Nhibernate (reading the book 'Nhibernate in Action'), I decided to build the same application in NHibernate as home practice.  This way I can see what the strong and weak points of both the frameworks are create a good comparison between them.

Let's go high level and write down the differences I can spot immediately without an in depth knowledge of NHibernate.

What is Allors

Allors is not a pure mapper tool, it is a 'Domain Driven Enabler'.  Your domain will be modeled (by using a "repository explorer") into a meta domain.  This results in having full control of all your complete domain information both at runtime and design time.  This meta information will be used to generate a database scheme for you (if you decide to deploy to a database, you can work completely in memory as well). 

All access to class variables goes through strategy objects.  These strategy objects are shielded from your domain class by means of Allors base classes (generated for you from the "repository explorer").  Your domain is thus heavily tied to the Allors platform.  All objects are completely managed, they are created within a session which acts as a unit of work/Identity Map that can be committed/rollback entirely.

Allors tries to make relationships first class citizens into the development process.  A relation should be considered as important as an object because a lot of what your object can or can't do depends on having such a relationship.  (For example consider a relation between a person and a email.  If that relation does not exists then the person cannot be emailed and a 'SendEmail' method on the object would make no sense).  Every relation between objects is completely managed, all relations are by default bi-directional and both ends of it are maintained by the platform (see later 'what I like about Allors).

Allors chooses to not give the developer all the options.  All the objects are loading with their value objects in them, in their strategy object that is.  Once you need a relation to another object that object (or many objects depending on your relation cardinality) is lazy loaded.  All access to variables is delegated to the strategy objects.  There the lazy loading can be initiated if possible (lazy loading is possible if your strategy is based on a database, if the strategy is disconnected you might nog be able to load it afterwards), nothing happens through code manipulation or reflection.

What is NHibernate

NHibernate's domain model has no links with other assemblies what so ever.  This is keeps your domain model lean and mean and involves no learning curve to build up your domain.  However persistence is handled through mapping files/attributes.  You can specify a ton of properties in here so it will take time to get this tweaked and completely learned.  The major benefit of this NHibernate is that it can work with both existing databases as newly created (your ddl can be generation from Nhibernate).

Focus with Nhibernate lies towards mapping your domain objects into your database.  It is focused on the object themselves and how to get them persisted into a database.  All the rest is just standard behavior of .Net, meaning that a bi-directional relation is considered as two separate ones.  You have to keep your domain in sync yourself.

As already mentioned NHibernate lets you specify the mapping between your object and your tables.  While doing so you have a ton of options you can set.  Different fetching strategies, Caching, setting variables through reflection (not exposing anything you don't want)...  All is possible.  It uses interceptors and code emitting to get all these things done without letting the persistence code into the domain.  To do this you have to take some things under consideration (always create default constructor, make properties virtual in order to have them lazy loaded, ...).  What ever happens with a sealed class??

High level Comparison Table

NHibernate Allors
Persistence Ignorant Managed Object
Object Driven Relation Driven
Lots of configuration and fine tuning possible Defaults by Allors
Both New and Existing database schemes Only Managed by Allors

What I like about Allors (compared to NHibernate with my current knowledge)

Check the following code:  Your domain model has two objects. A Person and an Address, the relationship between these two is a one-to-one, meaning that any address can be used for one person and one person can have only one address.  Some code:

1 Person person1 = new Person(); 2 Person person2 = new Person(); 3 Address address1 = new Address(); 4 Address address2 = new Address(); 5 6 person1.Addresss= address1; 7 person2.Addresss= address2; 8 9 // Switch one-to-one relation 10 person2.Address = address1; 11 12 // Check that managed objects are correctly relinked 13 Assert.IsFalse(person1.ExistAddress); 14 Assert.IsFalse(address2.ExistPersoon);

The above test would go green with Allors, but with I don't know what happens with NHibernate.  It will mostly depends on the mapping configuration you have specified.  If you make the FK index unique I think you will get a FK Constraint violation??  If you don't make it unique your domain model will go out of synch with what you are trying to do/or you would have to get the objects in memory en remove the associations from them.  I'll be doing a little spike in order of how these situations reaction (Still have to look into all the configurations possible before I can do that).

Where do we go from here

The next blog post will handle about the domain I will be building in order to make an adequate and more in depth comparison between both frameworks.  The domain model will be about an invoicing application build in the simplest of way. That way I can as well check how future proof both frameworks are when you want to extend/update your database schemes and do some refactorings.

See you next time.