In the comments of my previous post - Descriptive State Enumeration - Maxim Tihobrazov asked me to show how to map a state pattern with NHibernate; and I am more than happy to oblige! NHibernate Mapping Options I certainly don't claim to be an expert on NHibernate, but I do use it on a daily basis and I've solved my fair share of problems - including how to map a State pattern with NHibernate. According the NHibernate inheritance documentation, there are three core ways of mapping this pattern: - Table per class hierarchy (I've always called it "record per class") - each subclass (state instance, in this case) is associated with a specific record in a table. All of the subclasses are found in the same table.
- Table per subclass (also called a "joined sub class") - each sub class has it's own table and is joined to the super class by a one to one relationship with it's primary key.
- Table per concrete class - each sub class has it's own table and is mapped by the specific class type, not by the abstraction.
My Choice - Table Per Class Hierarchy In my current set of applications, I've always used the table per class hierarchy option so that is what I will describe here. My reasons for choosing this option are simplicity in the mapping and more importantly - not having a need to store the records in their own tables. Each of my states can be very cleanly represented as a record in my state table. Setting Up Shop Let's consider the same coffee shop that I've used in both of my PTOM posts - in this case, we're dealing specifically with the Order class and it's OrderStatus state. It's necessary to note that when I am working with NHibernate and the state pattern, I actually do set up my state models a little different. First off - we need an Id for NHibernate to map to the table's primary key. Secondly, I like to keep the core information in the abstract base class and provide all the varying values via a constructor. Third, each of the inheriting classes needs to provide a default (no args) constructor. Forth, we need a way for NHibernate to know which class it should actually instantiate, when loading - a "descriminator". I like to use a simple string Name property for this. And lastly - I think (though I am likely wrong on this aspect) that the abstract base class needs to provide a default constructor. Fortunately, the constructors we need don't need to be public - they can be private. With all of that being said, here is realistic code base that I would use for my Order and OrderStatus model: public class Order { public int Id { get; set; } public OrderStatus Status { get; set; }} public abstract class OrderStatus { public static InProcess = new InProcessStatus(); public static Totaled = new TotaledStatus(); public static Tendered = new TenderedStatus(); public static Delivered = new DeliveredStatus(); public int Id { get; set; } public bool DisplayForFullfillment { get; } public string Name { get; set; } private OrderStatus() { } private OrderStatus(int id, bool display, string name) { Id = id; DisplayForFullfillment = display; Name = name; } } public class InProcessStatus: OrderStatus { private InProcessStatus(): base(1, false, "InProcess"); } public class TotaledStatus: OrderStatus { private TotaledStatus(): base(2, true, "Totaled"); } public class TenderedStatus: OrderStatus { private TenderedStatus(): base(3, true, "Tendered"); } public class DeliveredStatus: OrderStatus { private DeliveredStatus(): base(4, false, "Delivered"); }
The Fluent NHibernate Maps
I'm a huge fan of Fluent NHibernate. I've been using it since just after it was branched from the original ShadeTree code. And at one point, I had submitted so many patches that I was made a comitter on the project. So, it should be no surprise to anyone that I'm going to advocate using FluentNHibernate over the .hbm.xml mapping files. Truth be told, I don't even remember how to do the needed descriminators in hbm.xml files. There was some trick to the descriminator being the first specified items after the Id or something... it's just easier to do in FluentNHibernate, so I don't bother with hbm.xml files anymore.
The Order map is going to be as standard as any other map. You don't need to do anything special here. Just map the Id of the Order and the referenced OrderStatus. (I like to add a "CreateMap()" method that is called from the constructor, so that I can avoid the "virtual method call from a constructor" warning. But I also use the "treat warnings as errors" option for my C# projects).
public class OrderMap: ClassMap<Order> { public OrderMap() { CreateMap(); } public void CreateMap() { DefaultAccess.AsProperty(); WithTable("Orders"); Id(o => o.Id).GeneratedBy.Assigned(); } }
It's really the OrderStatus map that is special in this case. This is where we get into the details of the descriminator - telling NHibernate which specific instance to create, when loading the data from the database. In our case, we have added a "Name" field to our OrderStatus object and we will be explicitly using this property as the descriminator.
public class OrderStatusMap: ClassMap<OrderStatus> { public OrderStatusMap() { CreateMap(); } public void CreateMap() { DefaultAccess.AsProperty(); WithTable("OrderStates"); Id(s => s.Id).GeneratedBy.Assigned(); DiscriminateSubClassesOnColumn<string>("Name") .SubClass<InProcessStatus>() .IsIdentifiedBy(OrderStatus.InProcess.Name) .MapSubClassColumns(x => { }) .SubClass<TotaledStatus>() .IsIdentifiedBy(OrderStatus.Totaled.Name) .MapSubClassColumns(x => { }) .SubClass<TenderedStatus>() .IsIdentifiedBy(OrderStatus.Tendered.Name) .MapSubClassColumns(x => { }) .SubClass<DeliveredStatus>() .IsIdentifiedBy(OrderStatus.Delivered.Name) .MapSubClassColumns(x => { }) Map(s => s.Name); } }
The key to all of this is the DescriminateSubClassesOnColumn method. The generics <string> tells us what .NET Type is being used to identify the specific class instances that we are dealing with and the ("Name") parameter is the column name in the database that represents the specific instance.
Then, we have the specific class instances referenced by the ".Subclass<type>" calls. This is a fluent interface, so ".Subclass" is a method that gets called directly off the DescriminatoeSubClassesOnColumn method. The specific type we want NHibernate to know about is specified in the generics parameter: ".SubClass<InProcessStatus>". The "IsIdentifiedBy" is where we give NHibernate the knowledge of what value in the "Name" column maps to what specific class in our code. To prevent magic string syndrome from setting in, I like to use the actual enumeration pattern of my OrderStatus to specify the string name of the class.
And finally, we have to provide an ugly workaround for our maps via the "MapSubclassColumns" method. There is an implementation issue in FluentNHibernate currently, and because of this issue we are forced to call the "MapSubclassColumns" method, with an empty lambda expression. If we don't call this method, the sub class will not get registered and NHibernate will not know how to handle the data in question. (I am hoping to fix this issue at some time, and make it so we don't have to call that method. I just haven't had time recently.)
Wrapping Up The Map
The rest of the map (the one remaining "name" property being mapped, in this case) is a regular NHibernate map. You'll notice, though, that we never mapped the "DispalyForFullfillment" property. I explicitly choose to leave any and all "volatile" properties out of my state maps. By doing this, I am able to add, edit, and remove any properties or methods on the state objects that I need, without having to change the NHibernate mappings. Since the state objects I am dealing with don't change without recompiling the code anyway, I don't need the ability to define them in the database. However, if you do need or want the flexibility of defining your states in the database, you can map each individual property of the state. Just remember that you will have to modify both the code and the database, to make the changes complete.
I hope this quick look at mapping a state pattern with Fluent NHibernate will help to shed some light on the subject, for someone. I realize that I have only provided the FluentNHibernate mapping, though. I specifically chose to do this because it would be a serious chore for myself to create a code project with NHibernate set up so that I can actually verify my mapping xml is correct. However, the translation from FluentNHibernate back to standard .hbm.xml files should be fairly straightforward, with the help of the NHibernate Documentation. If anyone out there is willing to help out and send me the correct .hbm.xml mapping, I would be more than happy to add it to this post and credit you with the work.
_________________________________ Cross Posted From LostTechies.com
In my last post, I talked about the idea of encapsulation and using it to ensure that our business rules were enforced correctly. What I didn't talk about, though, was the second half of the conversation that my coworker and I had, concerning the patent -> consultation relationship. It turns out that we had the relationship wrong. That's not to say that patients don't have consultations, but that the logical model we were traveling with had an incorrect perspective and was causing us to create some very ugly workarounds in various parts of the system. What really stuck out in my mind, though, was not the idea that we had the model wrong, but how we came to the conclusion of the model being wrong. It has become apparent to me, upon reflection of the conversations and situation as a whole, that design smells are not always evidenced by design related activities, if ever. A Persistent Problem - Duplicate Redundancy After some initial coding of the patient -> consultation relationship, we start working on the persistence model via NHibernate. What we have is a patient with a collection of consultations - this is easy to map with NHibernate's one-to-many capabilities. We also have a CurrentConsultation property which needs to be mapped. This property is mapped to the same Consultation table, but only pull one specific consultation based on the business rules that state the current consultation is chronologically the most recent and has not ending date set. After some thought, we found that there were a few possibilities for handling the CurrentConsultation property in our current model: - Create a "CurrentConsultation" object that is mapped to the Consultation table and use a "where" class attribute in the NHibernate mapping that would limit the returned result
- Create a "CurrentConsultation" object that is mapped to a CurrentConsultation view and have the view coded to return the correct consultation object
- Add a CurrentConsultationId field to the Patient table, as a foreign key to the Consultations table, and map to the existing Consultation object
After some additional thought, though, we found that each of these solutions has a few significant problems that were going to cause a lot of trouble. Options 1 and 2 Both of these options have the problem of duplicating business rules into more than one language and location. We would either have the business rules of what constitutes the current consultation in the NHibernate mapping (the 'where' attribute) or in a database view, in addition to the already existing code. Changing the rule would mean changing a minimum of two locations where that rule is handled. This is a bad idea no matter how you look at it. Both of the options have also created a duplication of knowledge from the concept of a Consultation by creating a "CurrentConsultation" class and a separate NHibernate map for it. We would have the original Consultation class and the new CurrentConsultation class both representing the same data, making an artificial distinction in our code. Again, this is a bad idea. We don't want duplication of these logical concepts. We're also not dealing with a bounded context or any other logical separation of concerns at this point, so there is no need to separate the concept of a consultation into multiple classes. Option 3 This doesn't appear to have the duplication issue in code, but there is a potential for duplication of data. When we get down to the implementation of NHibernate, we could easily cause duplicate data in the consultation table by saving the current consultation class. We might be able to get around this by not cascading the saves of the current consultation property, but then we'd be forced to ensure the consultation collection was persisted prior to the patient so that we could update the current consultation object's id before saving the patient. Both of these problems sound like a serious pain to me. I'm betting it's possible, but I'm also betting that it would be a nightmare of trial and error to get it right and a lot more code than we should really have to write. Changing Perspectives As Joe Ocampo pointed out in the comments of my original post, we had a problem in our system that was really caused by our lack of correct perspective in the situation. Rather than forcing the idea of a patient being the root aggregate in this situation, causing us a lot of headache and frustration in trying to model our persistence layer, a simple change in how we looked at the situation helped us solve the persistence problem and greatly simplified how the application worked. Joe's comment (with some formatting added): "One thing I like to challenge developers with when I teach DDD is to flip the aggregate to determine if the model is sound. I know this is only an example but work with me here. You indicated you are dealing with a medical system. We can assume there are certain entities such as Patient, Consultations, Doctor and Practice. In your example you created a model where the patient is the aggregate root for consultations but what if the Doctor simply asked what consultations do I have today? In this paradigm the Practice is the aggregate root and Consultations are aggregate within where Patient is an aspect of the consultation. The code would look something like this. consultations = practiceService(IConsultationService).GetConsultationsFor(doctor);
This also allows the consultation service to encapsulate its own logic for creating a consultation for creating a consultation. You can’t get any closer than that 
consultationService.CreateConsultationFor(patient).with(doctor).at(date);
The point I am trying to make is be careful of aggregate roots. Once you go down that path it is really difficult to back the train up and break it apart."
Though our actual implementation was different, this was the same basic conclusion that we had come to - our perspective on the situation was simply wrong. When we stepped back from the problem and realized that the consultation was the primary focus of the situation, and that a nurse or doctor would be the primary user of that portion of the system, it became rather obvious that our aggregate was in dire need of rework.
A Reflective Perspective
What we ended up with was a Patient object that dealt with all of it's demographics information, billing information, etc, without a CurrentConsultation property or even a Consultations list. Then, on the the separated Consultation object, we added a child property of Patient. Once we realized that our Consultation object was the primary focus and made this distinction in our code, we also realized that the Patient object was carrying far too much information around the system. We found that we actually had two very distinct concepts of a patient, determined by two very distinct bounded contexts.
- In the 'Billing' context, we needed all of the address , billing, and other demographics information about the patient - who they are, where they live, what their insurance is, etc. The existing Patient class filled this need.
- In the 'Consultations' context, we did not need anything from the Billing context, except for the person's name and patient id. What we really care about in the consultations is medical information about the patient - their current prescriptions, allergies, past medical care, etc. So, we created a ' patient' class to represent these needs.
These changed allowed for a much more clearly defined model that was truly reflective of the systems needs. We could easily see the difference between a 'billing' patient and a 'medical' patient, and we were able to code each of these areas of the system without the concerns bleeding into each other. Essentially, we decoupled the system at a module level, not just at a class level.
We also found that the NHibernate mapping problems suddenly went away. Since the Consultation class had a child of Patient, it was a simple many-to-one mapping with no strange sequencing or duplicate data issues. In the screens that deal with the consultation directly, we load the consultation as the aggregate root and go from there. In the screens that need to show patient consultation history, we did a simple query and returned all of the consultations for the given patient. Again, we found a way to decouple our system - this time, at the persistence model.
Design Smells: Not Just A Design Problem
In the end, we were able to recognize a serious design smell in our system - not by the design itself, though. After all, the original code had encapsulated the needs quite well. But, as it turns out, it was a bad encapsulation at a higher level. It wasn't until we started working with the model we had created, specifically trying to persist the model, that we realized our design was not right.
This change was a huge breakthrough for us, not necessarily in the code or the system that was being built, but in how we look at our systems and our domain models. The realization that design smells are often evidenced not by the design itself, but by how the design is used in the infrastructure and other supporting roles of the system, has had a profound impact on how we look at system design. I'm now seeing areas of different systems that are encapsulated incorrectly, at a higher level than class design. Recognizing the problem is the first step - and we're now working to rearrange and invert these models to more accurately reflect reality.
Pay attention to the pain that your application, infrastructure and other supporting services are causing you. You may be staring at evidence of a design problem, without realizing it.
_________________________________ Cross Posted From LostTechies.com
Some coworkers were recently working on an object model for a simple security system. After some discussion with them, we came up with this basic model: A permission is defined as an activity that can be assigned to a user, or group, and can be allowed or disallowed. From a Domain Driven Design perspective, we're stating that the Permission is the aggregate root. The User object itself, while involved in this aggregate, is divorced from this aggregate's relational model - you can load and work with a User object without having to load or worry about the Permission hierarchy. The addition of the "UserGroup" is solely for the many-to-many relationship between User and Group mappings with NHibernate and is not actually part of the object model's code. Once we had this model in place, we wanted to have a simple query that allowed us to load a given permission object by activity name, for a user - whether the user was assigned directly or via a group. At a high level, this model and query should be fairly simple to work with. It turned out to be a massive learning curve in NHibernate, though. After much trial, error, and Google searching, we ended up with this NHibernate query code: ICriterion userIdMatches = Restrictions.Eq("Id", userId);ICriterion activityNameMatches = Restrictions.Eq("Name", action);ICriterion userIdAliasMatches = Restrictions.Eq("u.Id", userId); DetachedCriteria groupPermissionCriteria = DetachedCriteria.For<Permission>() .SetProjection(Projections.Property("Group")) .CreateCriteria("Group").CreateCriteria("Users").Add(userIdMatches);ICriterion groupSubquery = Subqueries.PropertyIn("Group", groupPermissionCriteria); DetachedCriteria permissionCriteria = DetachedCriteria.For<Permission>() .CreateAlias("User", "u", JoinType.LeftOuterJoin) .Add(Restrictions.Or(userIdAliasMatches, groupSubquery)); permissionCriteria.CreateCriteria("Activity").Add(activityNameMatches); ICriteria executableCriteria = permissionCriteria.GetExecutableCriteria(Session); result = executableCriteria.List<Permission>(); return result;
There were a lot of lessons learned and a lot of parts that eventually got put together. Here's a quick run-down of what we ended up with and why.
- The ICriterion's at the top of this code block are there to provide a little better readability in the real query code below.
- The groupPermissionCriteria is set up to find a permission object where the specified userId belongs to a Group that belongs to the Permission - i.e. find permissions where the user is assigned via a group. The learning curve from this perspective was the SetProjection call. Though we are not entirely sure what a projection is at this point, we did find out that it was necessary for us to set this projection so that the detached criteria could be used as a sub-query.
- The groupSubQuery is the conversion of the groupPermissionCriteria into an ICriterion so that we can do a logical Or with it in the primary query construction.
- The permissionCriteria object sets up the core criteria logic and ties together the group permission assignment with the user permission assignment.
- CreateAlias is used so that we can shorten the criteria for loading by the User assignment directly. The JoinType on the alias needs to be Left Outer Join so that we will return a proper Permission object even when there is no direct user assignment.
- After creating the alias, we can add an "Or" criterion to the query and specify that we want to match based on the user's direct assignment or a group's assignment.
- The last line of the criteria simply adds the Activity criteria to load by the activity name.
The resulting SQL will load the permission by Activity name AND (User assignment OR group assignment where the user is part of the group).
SELECT this_.PERMISSIONSID as PERMISSI1_0_3_, this_.IS_ALLOWED as IS2_0_3_, this_.ACTIVITYID as ACTIVITYID0_3_, this_.USERID as USERID0_3_, this_.GROUPID as GROUPID0_3_, activity1_.ACTIVITYID as ACTIVITYID4_0_, activity1_.ACTIVITY_NAME as ACTIVITY2_4_0_, activity1_.DESCRIPTION as DESCRIPT3_4_0_, activity1_.INACTIVE_DATE as INACTIVE4_4_0_, u2_.USERID as USERID3_1_, u2_.USER_NAME as USER2_3_1_, u2_.INACTIVE_DATE as INACTIVE3_3_1_, group6_.GROUPID as GROUPID1_2_, group6_.GROUP_NAME as GROUP2_1_2_, group6_.INACTIVE_DATE as INACTIVE3_1_2_ FROM PERMISSIONS this_ inner join ACTIVITY activity1_ on this_.ACTIVITYID=activity1_.ACTIVITYID inner join USERS u2_ on this_.USERID=u2_.USERID left outer join GROUPS group6_ on this_.GROUPID=group6_.GROUPID WHERE activity1_.ACTIVITY_NAME = :p1 and ( u2_.USERID = :p2 or this_.GROUPID = ( SELECT this_0_.GROUPID as y0_ FROM PERMISSIONS this_0_ inner join GROUPS group1_ on this_0_.GROUPID=group1_.GROUPID inner join USERS_GROUPS users5_ on group1_.GROUPID=users5_.GROUPID inner join USERS user2_ on users5_.USERID=user2_.USERID WHERE user2_.USERID = :p3 ) )
Using a sub-query to load based on the group is not the most optimal way of loading the permission for the group assignment. However, since all of the joins in the main query and the sub-query are done on primary and foreign keys in the tables, performance should not be an issue. The only real performance concern for this query is the activity name in the where statement. A simple unique constraint and index on the activity name, though, will solve that problem.
Earlier this week, I was doing a training session with my current team, building a sample Org Chart application. One of the user stories we were working with, talked about assigning managers to department and had the following acceptance criteria: - When assigning a manager to a department, ensure that they are an employee of that department, and remove them from their old department
The basic code that we came up with worked well and was encapsulated into a service object in the domain. public class AssignmentService { private IDepartmentRepository _repository; public AssignmentService(IDepartmentRepository repository) { _repository = repository; } public void AssignManagerToDepartment(Employee manager, Department assignedDepartment) { Department previouslyAssignedDepartment = _repository.GetDepartmentForEmployee(employee); if (previouslyAssignedDepartment != assignedDepartment) { previouslyAssignedDepartment.RemoveEmployee(manager); _repository.Save(previouslyAssignedDepartment); } assignedDepartment.AssignEmployee(manager); assignedDepartment.AssignManager(manager); _repository.Save(assignedDepartment); } }
Problems
At first glance, this code seems simple enough, but we quickly ran into a problem when we started coding for another story and acceptance criteria.
- When creating a department, allow a manager to be assigned
Off-hand, this seems like a very simple situation. We'll just call the AssignmentService object after we create the Department. However, there's a large potential problem with this - the AssignmentService handles the entity persistence for both of the departments that were changed, when the manager is assigned. With that in mind, the developer working on the new requirement would have to ensure that the department is created with all of it's required fields and information before calling this service object. If the developer allows the manager to be assigned before the department's info has been completely filled in, we may accidentally allow an invalid state for a department object when calling the AssignmentService. Since the AssignmentService tries to save the department being assigned, we may also have created an semantic coupling between the department creation process and the AssignmentService - the developer needs to know that the AssignmentService will save the department. If they don't know this, they may try to save the same entities more than once, may try to save an invalid state, etc, etc, etc.
Unfortunately, we didn't have time to fix the problem during the training session - we had to stop the training almost immediately after we discovered this issue. I've got a few ideas running around in my head, on how to fix this, but I'm not 100% sure that I like any of them.
Possible Solutions
First and foremost, though, it's becoming rather apparent to me that we want to avoid entity persistence calls from within the domain service objects. This essentially means that the AssignmentService object would have to return the assigned department, and the 'previously assigned department' objects, so that the coordinating presenter could save them both. That seems a little odd, if you ask me.
One of my coworkers suggested that we allow the service to save the previously assigned department, since that is an self-contained operation in the method (the method loads the department, modifies it, and saves it) and then return the assigned department so that the presenter can save it. This option seems a bit better off-hand, but I'm still worried about the possible side effects of the AssignmentService saving the previously assigned department.
Questions
What are your thoughts on this situation... How would you solve the dilemmas that I'm pointing out? Is there a better design for the model that would eliminate this problem and make the department persistence more obvious, without having to 'know' when it is persisted?
Is the idea of not allowing entity persistence from a domain service object valid? Should we go with allowing the self-contained operations to persist the entity in that operation?
Or ???
I'm currently working on a search screen for a shipment tracking system. At the bottom of this screen, there are 4 checkboxes that will determine whether or not we are supposed to display Imports, Exports, Air shipments, or Ocean shipments - in whatever combination the user wants: Down in the depths of the search process, I am creating a "finder" object, as Ayende talked about quite a while back. In this object, I have to account for the checkbox values here - whether or not the user wants to display whatever types of shipment (import or export) and/or the method of shipment (air or ocean). What gets really interesting is that when a box is un-checked, I should not show that particular type or method. A quick analysis of these 4 checkboxes will come up with the following variants that must be accounted for when building the query. - Show Imports Only
- Ocean and Air
- Ocean only
- Air Only
- Show Exports Only
- Ocean and Air
- Ocean only
- Air only
- Show Imports and Export
- Ocean and Air
- Ocean only
- Air only
- Show no imports or exports (returns no results)
- Show no ocean or air (returns no results)
All totaled up, that's 11 different query variants that have to be accounted for. The end result of my query build methods is the following: private void AddShipmentMethodCriteria(DetachedCriteria criteria) { ICriterion air = Restrictions.Eq("ShipmentMethod", "Air"); ICriterion ocean = Restrictions.Eq("ShipmentMethod", "Ocean"); if (searchCriteria.ViewAirShipments && searchCriteria.ViewOceanShipments) criteria.Add(Restrictions.Or(air, ocean)); else { if (searchCriteria.ViewAirShipments) criteria.Add(air); else criteria.Add(Restrictions.Not(air)); if (searchCriteria.ViewOceanShipments) criteria.Add(ocean); else criteria.Add(Restrictions.Not(ocean)); } } private void AddShipmentTypeCriteria(DetachedCriteria criteria) { ICriterion import = Restrictions.Eq("ShipmentType", "Import"); ICriterion export = Restrictions.Eq("ShipmentType", "Export"); if (searchCriteria.ViewImports && searchCriteria.ViewExports) criteria.Add(Restrictions.Or(import, export)); else { if (searchCriteria.ViewImports) criteria.Add(import); else criteria.Add(Restrictions.Not(import)); if (searchCriteria.ViewExports) criteria.Add(export); else criteria.Add(Restrictions.Not(export)); } }
By having the first If statement check for both 'Import' and 'Export' being requested, we can properly create our query to show both of them via the Restrictions.Or() criteria. Additionally, we have to account for either or both of them not being checked and explicitly call them out to say that we do not want to show whichever one is not selected. The same is true for the 'Air' and 'Ocean' shipment methods.
The end result is that the user can select or un-select whichever shipment methods and shipment types they want, resulting in the correct data being displayed.
...
On a side note, there's probably some abstraction that I could create where I pass in the ICriterions and the boolean flags to help reduce code redundancy but that's not the point of this post. I'm really trying to illustrate the complex logic used in creating the correct NHibernate ICriteria/DetachedCriteria, and the analysis that you need to undertake for what looks like the most simple of situations. After all, how hard could it be to create a query based on 4 check boxes? ... more difficult than you might imagine, at first. Take the time to analyze even simple scenarios like this.
A coworker asked this question about our automated integration test suite, recently: "with the RowTest feature of NUnit, does the test data being used need to be hard coded into my code or can it be a variable that gets defined based on some rule?" Here is my response, outlining the need to know the state of the database (know every value of every field, in every table) before and after the test suite is run: The short answer is that the tests will have data hard coded into them. This may be a change from how you have looked at integration testing, previously – where the data is changed on each run, so as not to duplicate the data in the system. Rather than changing the data that is used during each test, we need to ensure that the data is exactly what we expect, before and after each test. If the data is not what we expect before the test is run, the test cannot produce the data that we expect to have after it is run. If the data is not what we expect after a test is run, the test fails. At a very high level, automated tests against the database require three things: - A known, state of ALL data in the database
- A known, suite of unit tests that manipulate said data in predictable ways
- An expected, verifiable suite of data returned by the software and manipulated in the database
The data is not necessarily "perfect" in that it has no flaws in it – rather, every last character in every last field of every last table is known, and all of the ramifications of that data is known with the explicit purpose of that data being there to support the automated tests. The automated tests expect this "perfect" data to produce the desired results and verify that the system works as expected. Any data that is manipulated in the system is expected to be in a predictable state, so the tests can verify that the system manipulated the data correctly. When it comes to implementation of this, there are some fairly strict requirements for it to work: - Before the first test is run from the test suite, the data must be exactly what the tests expect
- After the tests have run, the data must be exactly what the tests expect
- Repeat for each run of the test suite
What this really means is that we cannot add or modify data between two test runs, without changing the tests that work with the data. In order for the tests to run multiple times throughout the day and/or on-demand, we have to ensure that the data being manipulated is what we expect it to be. Therefore, the first step of any run of the test suite is to revert the database to the known state, through sql scripts or code that are automatically run before the tests are run.
Myself and 10 other developers in my company went through a day of BDD / TDD training, with Scott Bellware, yesterday. It was a lot of fun, very challenging at times, and covered a lot of topics including an overview of Agile and Behavior Driven Development, all the way down to writing Specification Tests, doing Test Driven Development and refactoring the model to improve readability, maintainability, flexibility, etc. I took notes via index cards (love that cool-aid) and wanted to share. I don't expect these notes to make sense to everyone. Hopefully it will spark some dialog in someone's mind and cause them to dig further. First off - the quote of the day. "Can I be honest with you and say that I've been wanting to touch your keyboard, all day?" Now for my notes.  User Stories [Role], [Goal], [Motiviation] - As a [role], I want to [goal], so that [motivation]
- Example: As a nurse, I want to record a patients vital signs, so that I can determine their medication and care needs
- Motivation is critical - it determines how the development team understands and implements the story. It determines the user experience, how things are integrated, how the software is designed, etc.
Acceptance Criteria - Acceptance Criteria is used to drive code, not the story, directly
- may change at any point, up to implementation
- is used to drive code design, test design, implementations, etc.
- should be spoken in domain language
- may include non-functional, technical details such as database tables, infrastructure, performance, etc
- All acceptance criteria must be met and tested / verified before a story is considered done
Specification Tests - Test Fixture per Class is an anti-pattern (on a personal note, this problem bothered me for months before I discovered BDD)
- Context Specification or Behavior Specification testing
- When [verb] then [verb]
- "When [verb]" is the context
- "Then [verb]" is an observation of the behavior
- Based on Acceptance Criteria, but not "code-gen'd" from acceptance criteria
Story Estimation - Agile Poker: uses generalized Fibonacci sequence as order of complexity
- "?", 0, 1/2, 1, 2, 3, 5, 8, 13, 20, 40, 100, infinite
- everyone throws their estimate at same time
- if estimates have significant outliers, discussion occurs to understand why, get more detail, etc. and re-throw may happen
Entity Data vs. Aggregate Data - Entities should never contain aggregate data
- Aggregate data is for reporting and other aggregate needs
- If you need aggregate data to process something, write an SQL query, stored proc, etc. - don't use an ORM like NHibernate
- We don't want a "Customer" entity to need 10,000 "Order" entities, to aggregate data for processing; write a query to aggregate instead
- We don't want to persist data that can be calculated / aggregated, generally (performance issues may override this)
Domain Services - Can have dependencies on external systems
- are part of domain logic, therefore are in domain model / assembly
- are "Doers" of process that don't fit into entity and entity logic, directly
- coordination of entity logic
- can include calls to data access, logging, etc.
Continuous Integration - Not just continuous compilation of code
- Full end to end integration of all code, components, databases, services, etc
- Full suite of integration testing including database testing
- Do not allow commits if build is currently broken
- do not allow defects to live - fix immediately, to fix build
- "Defect" is broken software, "Bug" is functional but wrong
Daily Scrum - 3 Questions everyone answers:
- What did I do yesterday?
- What am I doing today?
- What issues am I having?
- Each person should answer quickly - 1 or 2 minutes, max
- further discussion happens outside of the Scrum meeting
Productivity of Dev Team - RAD and other non-review, non-iterative based management causes problems and loss of productivity
- we need constant review of the design to ensure good design
- shorten the feedback loop and get constant review of the design, to always improve the design, via pair programming, work cells, retrospectives, etc.
- good design will cause productivity gains in the development team beyond the capabilities of any tools
Whiteboard Diagramming vs. Details Specs - White board diagramming and human interaction is always better than detailed documents and specs in UML
- Human interaction leads to knowledge crunching and learning, not just reading a repeating
- Take pictures, don't re-draw in UML; don't waste time with it
- Video the entire conversation is even better, so others can learn from the knowledge crunching that occurs; capture the human interaction, body language, etc.
In my previous post, I talked about my base repository class that I use, to abstract the NHibernate details away from the actual repository. Now that I have the Do and DoTransaction methods to further the abstraction, I thought it would be good to share my whole abstraction. using System; using NHibernate; using NHibernate.Cfg; namespace RepositoryBase { public abstract class Repository : IDisposable { #region Vars private static readonly Configuration _config; private static readonly ISessionFactory _factory; #endregion #region Constructor / Destructor static BaseDAL() { _config = new Configuration(); _config.Configure(typeof(BaseDAL).Assembly, "RepositoryBase.hibernate.cfg.xml"); _factory = _config.BuildSessionFactory(); } ~BaseDAL() { Dispose(); } #endregion #region Properties public ISession Session { get; private set; } public ITransaction Transaction { get; private set; } #endregion #region Methods protected void Do(Action unitOfWork) { try { OpenSession(); unitOfWork(); } finally { CloseSession(); } } protected void DoTransaction(Action unitOfWork) { try { OpenSession(); BeginTransaction(); if (unitOfWork != null) unitOfWork(); CommitTransaction(); } catch { RollbackTransaction(); throw; } finally { CloseSession(); } } #endregion #region Helper Methods private void OpenSession() { if (Session != null) return; Session = _factory.OpenSession(); Session.FlushMode = FlushMode.Auto; } private void CloseSession() { if (Session == null) return; if (Session.IsOpen) { Session.Close(); } Session.Dispose(); Session = null; } private void BeginTransaction() { ValidateSession(); Transaction = Session.BeginTransaction(); } private void CommitTransaction() { ValidateSession(); if (Transaction != null) Transaction.Commit(); CloseTransaction(); } private void RollbackTransaction() { if (Transaction != null) Transaction.Rollback(); CloseTransaction(); } private void ValidateSession() { if (Session == null) throw new ApplicationException("NHibernate Session Not Open."); } private void CloseTransaction() { if (Transaction == null) return; Transaction.Dispose(); Transaction = null; } #endregion #region IDisposable Members public void Dispose() { CloseTransaction(); CloseSession(); } #endregion } }
Note that I am using an embedded resource as my "hibernate.cfg.xml" location. Just change this line to use a file system resource, or whatever you want for your hibernate configuration.
I'm still wanting to re-abstract this into a set of objects that let's me be concerned with transactions and queries at a business level. One step at a time, though. The syntax that I would like to see, at the moment, would be something like this:
public void SomeBusinessValue() { Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); Repository.DoTransaction(() => { SomeRepository.Save(something); SomeOtherRepository.Delete(somethingElse); }); }
And if I get really ambitious, I may try to incorporate Castle.Windsor's Automatic Transaction Facility, so that I can have syntax like this:
[Transactional(Transaction.Requires)] public void SomeBusinessValue() { Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); SomeRepository.Save(something); SomeOtherRepository.Delete(somethingElse); }
Of course, the more I travel down this path, the more obvious it is how Rhino.Commons' Repository came along. Heh - I'm only about 2 steps away from completely re-doing Ayende's UnitOfWork.
In fact, I may try to re-use NHibernateQueryGenerator and go for this syntax:
[Transactional(Transaction.Requires)] public void SomeBusinessValue() { Something something = Repository<Something>.Find(Where.SomeProperty = someValue); Something something = DoSomething.BusinessRelated(); SomethingElse somethingElse = SomethingElse(); Repository<Something>.Save(something); Repository<SomethingElse>.Delete(somethingElse); }
Wouldn't that be fun... I really do like this syntax. In fact, I spent 9+ months working with it on a project because I love the simplicity of the syntax. In the end, though, I did not understand all of the underlying architecture and abstraction and Ayende set up (he includes the ability to plug in any DAL, including Castle.ActiveRecord, NHibernate, etc) and it caused headaches for me.
...
The major problem I have with this syntax, at the moment, is unit testing the Repository object. I either need to hide these details behind an IWhateverRepository interface (complete with Save, Delete and GetByWhatever methods) like I have been doing, or I I'll need to learn how to unit test NHibernate with in-memory database or something... We'll see where this leads.
(side note: how's that for a "ThoughtStream". )
For the last few months, I've had a very small base class that abstracts out the NHibernate configuration, session creation, etc. It works very well, but is very limited in what it can do. basically, every method in my actual repository implementation would have to open a new session, execute a criteria and close the session. A typical implementation would look like this: public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; try { OpenSession(); invoices = Session.CreateCriteria(typeof(Invoice)).List<Invoice>(); } finally { CloseSession(); } return invoices; }
That certainly is easy and keeps the code fairly clean, removes a lot of duplication, etc.
However, there is a significant limitation - I can't have any code re-use for multi-query scenarios, without duplicating code. In other words, if I want to load that list of invoices and then load some other collection from another repository, I have to use two different Sessions across two different repository implementations. This really becomes an issue when dealing with transactions - I want my entire change set to pass or fail in a single transaction. In my current abstraction, this can't be done.
Fortunately, NHibernate has the solution to my dilemma built right in - all I need to do is create my criteria objects without a session, and then I can execute any / all of them from any session that I want.
public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; try { DetachedCriteria criteria = DetachedCriteria.For<Invoice>(); OpenSession(); invoices = criteria.GetExecutableCriteria(Session).List<Invoice>(); } finally { CloseSession(); } return invoices; }
I don't have a complete abstraction of the separate execution, yet. However, a very basic implementation could look like this (idea stolen from Ray Houston):
protected void Do(Action unitOfWork) { try { OpenSession(); unitOfWork(); } finally { CloseSession(); } } public ICollection<Invoice> GetAll() { ICollection<Invoice> invoices = null; DetachedCriteria criteria = DetachedCriteria.For<Invoice>(); Do(() =>{ invoices = criteria.GetExecutableCriteria(Session).List<Invoice>(); }); return invoices; }
This simple abstraction provides a lot of benefit for us.
- Eliminates duplicate code (not calling OpenSession / Close Session from all repository methods)
- Removes ugly Try / Catch blocks from Repository methods
- Allows multiple Criteria to be executed from a single Session / Transaction
And most importantly - this gives us the ability to create a better abstraction of NHibernate, to support business level transactions, not just repository level transactions. Obviously, this simple example is not going to provide business level transactions. It does get us down the path, though.
In the last few months, I've heard a person say this multiple times "business runs on de-normalized data" The context of this statement has always been the discussion of database design and implementation... and I continue to wonder where this person's experience is coming from, to make such a statement. In my world - the business of developing software to run a business or automate a portion of a business - this is very far from the truth. My response to this statement is (and yes, you can quote me on this - please do) Management reports on de-normalized data, but operations runs entirely on well-normalized data I'm not going to make any ignorant or naive claims about de-normalized data having no place in business. There certainly is a lot of business value in de-normalized data. That's why we have data warehousing, OLAP cubes, and other reporting database structures (including Views, Stored Procedures, etc. that will de-normalize data for live reporting). When it comes to the day to day business, though - the people on the floor doing the low level business work - well normalized data is an absolute must. From WikiPedia's entry on Database Normalization: Database normalization, sometimes referred to as canonical synthesis, is a technique for designing relational database tables to minimize duplication of information and, in so doing, to safeguard the database against certain types of logical or structural problems, namely data anomalies. For example, when multiple instances of a given piece of information occur in a table, the possibility exists that these instances will not be kept consistent when the data within the table is updated, leading to a loss of data integrity. A table that is sufficiently normalized is less vulnerable to problems of this kind, because its structure reflects the basic assumptions for when multiple instances of the same information should be represented by a single instance only. I'm certainly not a DBA and I'm not a real database guru. I do have more than 10 years experience working with various database systems (SQL Server, Access, Oracle, SQLite, MySQL, DB2/DB400, and XML & flat files , etc.) in various business scenarios (manufacturing, engineering, business process automation, e-commerce/e-business, enterprise integration, etc. etc.) and I have a pretty high opinion of my relational modeling capabilities ... and It evokes a sense of disbelief and shock when I see poorly normalized schema's or hear statements like this being made. Please, please, PLEASE take the time to understand what well normalized database design is and why it's necessary for flexible, maintainable software.
This might be common knowledge for those that use NHibernate - but it's new to me, since I am not an NHibernate power-user, yet. Given this object model: public class Invoice { private int _id = 0; private string _invoiceNumber = string.Empty; private DateTime _invoiceDueDate = DateTime.MinValue; private ICollection<InvoiceDetail> _invoiceDetails = new HashedSet<InvoiceDetail>(); public int Id { get { return _id; } } public string InvoiceNumber { get { return _invoiceNumber; } set { _invoiceNumber = value; } } public DateTime InvoiceDueDate { get { return _invoiceDueDate; } set { _invoiceDueDate = value; } } public ICollection<InvoiceDetail> InvoiceDetails { get { return _invoiceDetails; } } public InvoiceDetail CreateDetail() { InvoiceDetail detail = new InvoiceDetail(); InvoiceDetails.Add(detail); return detail; } } public class InvoiceDetail { internal InvoiceDetail() { } private int _id = 0; private string _productName = string.Empty; private decimal _productCost = 0; private int _productQuantity = 0; public int Id { get { return _id; } } public string ProductName { get { return _productName; } set { _productName = value; } } public decimal ProductCost { get { return _productCost; } set { _productCost = value; } } public int ProductQuantity { get { return _productQuantity; } set { _productQuantity = value; } } }
We can load any invoice that has an Invoice Detail with a specific Product Name, using this NHibernate code:
invoices = Session.CreateCriteria(typeof(Invoice)) .CreateCriteria("InvoiceDetails") .Add(Expression.Eq("ProductName", productName)) .List<Invoice>();
Additionally, we can add paging to the result set, by including the SetFirstResult and SetMaxResult calls in the Criteria.
invoices = Session.CreateCriteria(typeof(Invoice)) .CreateCriteria("InvoiceDetails") .Add(Expression.Eq("ProductName", productName)) .SetFirstResult(pageSize * pageNumber) .SetMaxResults(pageSize - 1) .List<Invoice>();
Notice the use of pageSize and PageNumber to set the First Result value - this creates an index-by-zero page number that we want to view. For example, if our page size is 10 and we are on page zero, then the FirstResult is going to be 0 and the last result will be 9. 0 through 9 = 10 results. If we are on page 3, then the FirstResult is 30 and the MaxResult is 39...
Works pretty well... now, let's just hope that the actual underlying database implementation of this can take advantage of each DBMS' optimizations for doing this.
|