I'm Tom Jaeschke and you may contact me at tomjaeschke@tomjaeschke.com or (512) 419-8788.

Sunday, November 16, 2008

How to Create Tarantino Objects


Strictly speaking, the Tarantino Project is a grab bag of useful .dll files for technologies such as NHibernate and StructureMap which are conveniently collected in one place (http://code.google.com/p/tarantino/) and which may be used in a .NET application.


That said, it is fairly safe to predict that a Tarantino application will be established in an onion architecture in which a given solution will have separate projects for Core, Core.IntegrationTests, Core.UnitTests, Database, Infrastructure, Infrastructure.IntegrationTests, Infrastructure.UnitTests, UI, and, perhaps, UI.Controls. It took me a little while to truly feel comfortable in an onion environment and to understand how all of the Tarantino tools come into play. Now that I "get it" however, I am sold.


For others who find their eyes tearing at cutting their first Tarantino onion I offer this suggestion: Set up a dummy Tarantino application and create an object. You will feel much better once you have done so.


Recently, I worked on a web site which sends email notifications whenever a visitor completes a web form. I thought it might be wise to record the data involved for each sending to a database. What follows, are the steps I undertook to do so. I feel this scenario illustrates fairly well how to create a Tarantino object.





  1. The first step I undertook was to decide on a name for my object. I chose "OutgoingEmailAttempt." In an Agile setting the name should match the term the customers use for the correlating concept so that there is no translation needed between terms discussed aloud and terms nested in code (where the name of the object will appear in all of the file and class names associated with the object, to ensure logical designations which are not painful for anyone to understand).


    With a name in mind, I then created a database table that was also called "OutgoingEmailAttempt" in Microsoft SQL Server Management Studio Express. I was careful to use the "Generate Change Script" feature to save the SQL from the table creation, and within my Tarantino application, I put the .sql file in the Database project within the "Update" folder.


    Why is it important to retain the SQL scripts? If one has a record of the SQL scripts used to create a schema, one may thus recreate the schema elsewhere. Tarantino applications typically employ multiple instances of their database schemas. There will be a separate database running in each individual developer's work environment, a separate database in a staging environment, a separate database in the production environment, etc. It is common to have a database table devoted to a list of scripts which have already been run and a build process in which scripts which are newly introduced get run before being logged to the table themselves. By way of this configuration, when the SQL for creating a database table for an object is checked into the code repository, the table will soon exist at each individual developer's work environment, a separate database in a staging environment, a separate database in the production environment, etc.


    The columns in my "OutgoingEmailAttempt" table each corresponded to a property for the "OutgoingEmailAttempt" object. I created varchar data type columns named NameOfParty (who completes a web form), EmailAddressOfParty, PhoneNumberOfParty, CommentsOfParty, IpAddressOfParty, InHouseNotation (for denoting why the email alert was sent), and InHouseEmailAddress (which clarifies which address the email alert was sent to). In the schema design: The columns should have the same names the object properties will ultimately have and the names should make sense. I also made a datetime data type column called ContactTime, and, most importantly, I made a uniqueidentifier data type column dubbed OutgoingEmailAttemptId. In every Tarantino application I've yet seen, the name of the uniqueidentifier column must be: the name of the object + "Id"








  2. The second file made was a class for the object itself titled "OutgoingEmailAttempt.cs" in the Core project under the "Model" folder. An object class such as this one should be made largely void of code so that a test for the class may be written before the real guts of the class are fleshed out. Try to write a class just complete enough to be referenced by a test and still compile without throwing an error.






  3. The third file is "OutgoingEmailAttemptTester.cs" in the Core.UnitTests project under the "Model" folder.


    The tester class may entail numerous unit tests. For any one test, the first driver in ping pong pairing must refine the object class in question enough to make a test against it fail without throwing an error and must also refine the test itself enough to end with a failing test which is strong, while the second driver is charged with making the test past. The pairing partners should work back and forth in this manner to write as many tests as are needed.


    Things which one should obviously add to an object class and test against are property accessors. In my class I wrote property accessors for NameOfParty, EmailAddressOfParty, PhoneNumberOfParty, CommentsOfParty, IpAddressOfParty, InHouseNotation, InHouseEmailAddress, and ContactTime. I used names identical to names given for corresponding columns at the database schema. I made InHouseEmailAddress a string variable, ContactTime a DateTime variable, and so on. I did not write a property accessor for OutgoingEmailAttemptId because my OutgoingEmailAttemptTester.cs class inherits from another class, PersistentObject.cs, which manages the "Id" accessor and a few other things common to all objects which inherit from it.






  4. The next step was to create a NHibernate mapping file to allow the Tarantino application to interact with the database table which was just created. I made OutgoingEmailAttempt.hbm.xml in the Infrastructure project within the "Mappings" folder (which, in turn, sits in the "DataAccess" folder). When undertaking such a process, it is best not to focus on getting the mappings accurate initially, but rather to just set up a placeholder file. The accuracy will be hammered out in the next step.


    It is crucial to set the "Build Action" property of an .hbm.xml file to "Embedded Resource" instead of the default value of "Compile." This may be done by right-clicking on the file in the Solution Explorer within Visual Studio and selecting "Properties" from the menu which appears.






  5. The fifth file I created was an integration test class called "OutgoingEmailAttemptMappingsTester.cs" in the Infrastructure.IntegrationTests project within the "Mappings" folder (which, in turn, sits in the "DataAccess" folder). Upon the successful completion of a run of a mappings test to add an entry, one should be able to see a row created at the corresponding database table. Obviously, if the mappings are inaccurate in the .hbm.xml file, the mappings test will fail when run and the .hbm.xml file will have to be refined in order to make the test pass.


    When undertaking ping pong pairing, one developer, the driver, will write, first, a hollow .hbm.xml file and, secondly, a mappings test which cannot pass (due to the emptiness of the file it tests), while the driver's partner, the navigator, will observe and speak aloud thoughts and suggestions. Once the driver and the navigator agree that the test is solid, then the two developers switch roles. The navigator becomes the driver and refines the .hbm.xml so that it will pass the mappings test, while the previous driver becomes the new navigator and offers verbal direction and a critical eye.


    I'm not going to dive into specific syntax for C# (the language used in Headspring Systems' Tarantino applications) or NHibernate in this article, but I also don't really have to. When creating the files for a new Tarantino object one can easily gauge a sense of what needs to be written by looking at the corresponding files for other objects in either the same solution or another Tarantino application. When in doubt, look to the existing work of others for guidance.






  6. In the Infrastructure project, I created "OutgoingEmailAttemptRepository.cs" within the "Repositories" folder (which, in turn, sits in the "DataAccess" folder). Repository classes, facilitate the casting of database data to objects and likewise the pushing of object data to databases.






  7. In the Infrastructure.UnitTests project, I created "OutgoingEmailAttemptRepositoryTester.cs" within the "Repositories" folder (which, in turn, sits in the "DataAccess" folder). As you might imagine, this class tests my repository class. At this point in the article, you may also be able to imagine how two programmers may write both a repository class and the unit tests to test it by way of ping pong programming.






  8. The last files I made were some web pages in the UI to use my new OutgoingEmailAttempt object and one interface.


    My Tarantino application is set up so that the UI project references both the Core and Infrastructure projects and the Infrastructure project references the Core project. So then, how may "OutgoingEmailAttempt.cs" utilize "OutgoingEmailAttemptRepository.cs" for database integration? The answer is by way of StructureMap and the creation of an interface within the Core project. I made "IOutgoingEmailAttemptRepository.cs" within the "Repositories" folder which sits within the "Model" folder within the Core project.


    "OutgoingEmailAttemptRepository.cs" was made to inherit from "IOutgoingEmailAttemptRepository.cs" and both files reference StructureMap in their using statements.





Conclusion: The steps above detail the sum of what needed to be done in establishing an object in a Tarantino application. Outside of the UI files I made to actually use the object I created, I needed to create the eight files I've detailed. The steps are well worth taking. I feel this is a much better approach to writing a .NET application than making a few god classes in the App_Code folder. ;)

No comments: