I saw Ryan Vice speak on Domain Driven Design at the Austin .NET Users Group on Monday. DDD is the brainchild of Eric Evans and in "Domain-Driven Design: Tackling Complexity in the Heart of Software" he asserts that a project is doomed if one does not first get the business and developers to use a common language when speaking of objects. Ryan suggested that he really does not see this miracle happening in real life all that much and that instead he finds that a QA process eventually brings the business and the development into alignment. He feels that DDD is thus useful in how one approaches development more than in whiteboarding what is going on with that guy with the glazed over eyes who writes the checks. Ryan points to "Patterns of Enterprise Application Architecture" by Martin Fowler wherein Fowler offers some ammunition that reinforces this theory. Fowler suggests that if a challenge is small (simple) that one should just use a transaction script. Otherwise however, one should use a domain model. That's right, it can make a difference for you standalone even if it doesn't help your communications with a stakeholder. Ryan contrasted the difference between a domain model and the anemic model that typically develops in its absence while arguing that the former is better. He also offered a third approach, CQS. The three models:
- Anemic Model: Herein "domain" objects just have getters and setters, but no other logic. They are fleshed out enough and just enough to be hydrated from NHibernate, and we are using the familiar "Repository" pattern to facilitate that. (Ryan admitted that he did not know what the formal name for this pattern is if there is one. I've heard it referred to as the Repository pattern before. In it there is one God class for all database interactions for one particular anemic object or, in the case of the Domain Model approach, domain object.) Our objects are Data Transfer Objects (DTOs). All mechanics for lifecycle and sanity checking and the like are pulled out to a services layer which seemed to be a separate Visual Studio project in the code examples Ryan showed off. (This was true across all three patterns.) If you wanted to make sure that when a person object was newed up that it got a first name and a last name, then you'd have to write a separate class for sanity checking the Person object creation process.
- Domain Model: Is like the anemic model, but you won't need a separate class for sanity checking the Person object creation process. Person, as defined in C#, will get a little fatter. It's default constructor will be made to be of protected accessibility in lieu of public accessibility so that NHibernate may still use it. However, this walls the constructor off from code monkeys coding against it (unless they do something stupid with inheritance tricks) who will have to use different public constructors. A good public constructor for Person based on what we've kicked around so far would be one that takes in two strings for first name and last name and turns around and assigns them to the FirstName and LastName getsetters. Speaking of: public string FirstName { get; set; } ...may now become: public string FirstName { get; private set; } ...and whenever there is a need to change FirstName, one will have to go through a method on Person to do so. Not only does this get domain object-specific logic out of the service layer, but it also keeps it from easily bleeding back to there. Other convenience methods such as FullName() which concatenates FirstName and LastName could be kept at the object. If you want to make sure an Age for a Person is not a negative number, again, this goes at the object and not at the services. Ryan feels this makes the code a lot more readable for someone ramping up on a project. In his anemic model's unit tests there were many unit tests for services which were really not to do with the service but with an anemic object. Tests for ensuring exceptions were thrown if FirstName on Person was left null, etc. In the domain model, this confusion and noise is reduced. Ryan mentioned bounded contexts in passing and I'd bet these would also be less painful in the domain model approach. (The quintessential Eric Evans example for a bounded context is one for "orders" in which the word may be used to mean one thing at the shopping cart within a large system and something else with regards to how goods exit the warehouse. There could be two separate lifecycles and different getsetters instead of making one fat Order object. Conceptually, this means two bounded contexts.) So what goes in the services if not domain object rule enforcement? The use of an external dependency such as the sending of an email is not a good example. Ryan recommended looking into the Domain Events model of Udi Dahan for addressing emailing. (raise a domain event and have a default handler which will throw an event on a queue) Jimmy Bogard has further refined this stuff. (send a message and chain event handlers onto the message) Orchestration of cross-talk to external dependencies goes in the services layer. In goofing off with MongoDB, I once put together a silly little app with a Repository pattern. As a convenience, I wanted a new user to be created as an administrator if that was the first user created for the application. Otherwise, a new user should just be a plain Jane user. The decision making for if a new user should be an administrator did not go in my repository. I pulled it up to my core logic so that I might get it under unit testing independent of external dependencies. I believe in that scenario I just handed in an interface which could be implemented by my repository when the app was run or could be stubbed with dummy POCOs in testing. This is an example of stuff to pull up to the services from the infrastructure. Ryan seemed to admit feeling conflicted about whether or not to hand in dependencies as interfaces as suggested in my example, but confessed that he did it all the time in logging. He did not elaborate on other alternatives. Perhaps these circumstances beg for bigger solutions such as the Domain Event solution for emailing??? Mark Seemann doesn't want you to inject cross-cutting concerns as dependencies.
- CQS: Not to be confused with CQRS which stands for Command Query Responsibilities Segregation wherein there are separate databases for writing (normalized) and reading (denormalized), CQS stands for Command Query Segregation and demands that you should write code for reads and writes in two different ways on the C# side. Separate things into commands and queries. Have an abstract class (template pattern) and then override the class with object implementations for the acts. I surmise there would be two separate base classes for commands and queries. Prep a command in newing it up, but ultimately have an independent .Execute method for ultimately running Session.Save(Whatever); too. In getting away from the repository classes and being tied to NHibernate tooling, one is now free to cherry pick how one queries. NHibernate could be used most of the time while complicated searches could be done with stored procedures which are much faster to write and optimize than HQL equivalents.
Other things that came up in this talk:
- SourceTree by Atlassian is a Mercurial/Git client.
- The "Set Design Approach" for improving quality was mentioned by a gentleman in the audience. I tried to Google it but found nothing. Maybe I have misheard the name???
- Service Locator was again poopooed as inferior to Dependency Injection. Duh.
No comments:
Post a Comment