Tuesday, August 19, 2008

Mass Refactoring of a Poor DDDD Stack

We are at a cross roads on our current project where:
• We are hitting limitation of our current architecture and framework
• We need to be able to facilitate service orientation and be come part of an SOA environment
• We are not completely swamped with work
• Bugs are lingering in a hard to understand convoluted stack

We have an NHibernate /Oracle based distributed (client - server) application.
Current concrete problems:
• We pass entities around across service boundaries. These entities can be huge and are therefore very slow to pass around and we hit upper limits on WCF message size (semi)regularly. The screens we are displaying show a lot of related data and the load time for some pages is insanely slow.
• Our application has some smarts in it that can be argued are actually domain logic, not application logic.
• We have a lot of code repetition “copy and paste mania” through out the stack
• We have a tightly coupled stack; using doubles/mocks/fakes at times is near on impossible meaning test are integrated, brittle and slow
• Services expose too many finely grained functions, many of which are not appropriate as public calls.
• We have one domain which needs to be spilt to shared kernels
• We have a very anaemic domain. Our domain objects are treated a bit like strongly types datasets with the service controlling business logic and flow.
• Poor separation of concerns both vertically and horizontally (i.e. mega classes)
• subtle and painful circular references
• Logic in views


DDD
• Clearly define boundaries. Implicit knowledge should not be required. Code should be fluent and readable. Refactoring is therefore a necessary component of building a fluent domain
• Flesh out the actual business problem and map it completely and deeply into the domain code. Reading a domain entity class it should obvious what it can do and what is cant.
• Assert your assumptions, use DBC, predicates, valuators and factories, especially with complex entity construction
• Domain entities are smart. Do not let the smarts of these domain object leak into the repositories or services (ie an anaemic domain)
• Repositories are just that, effectively a bucket holding domain objects. If you want to do complex DB logic use a report data access call, don’t muddy the repositories with reporting.
• Services are aggregators of domain functionality. They control flow and coordination between Repositories, Factories and Domain objects. They do not hold leaked domain logic.
• Reports are anything that is not domain object based: Actual reports, Scheduling, Complex db searching etc. These may return objects but they are not smart domain entities, nor are they are domain value types and so should not pollute the repositories.
• Value types are immutable and so consider whether they are suitable to be structs (e.g. time code)
• Reference Value types are read-only (i.e. only selectable form the database, never CUD) and so do not need mapped “created/last modified“ properties. These may be necessary in the DB (for standardisation) but this does not mean they need to be leaked into the domain


Distribution
• The message and service should be verbose enough to describe the functionality.
• Use messages. Don’t pass entities around across boundaries.
• Messages are immutable, don’t attempt to modify request messages on the recipient, nor should you modify response on the caller
• Feel free to reuse structures ie DTO but don’t reuse messages at all. A message is bound to a service and a function
• A message is the object passed across the wire which holds a DTO, which may be in turn made up of more DTOs. A message implements either IRequestMessage or IResponseMessage
• On entry to the server side any instrumentation/auditing/logging/security checks etc should be handling in a standardised reusable manner, consider AOP or delegate passing.
• On entry to the server side transactions/sessions should be handled as this is the entry point to the domain and a service call should be considered atomic.
• Faults, not exceptions, should be passed back to the caller.
• Consider whether the use of an ESB could improve the use of your domain. Message based communication is necessary if this is to be implemented.

Application
Logic in an application is bound to application specific flow. The exception here is validators. [I am yet to figure out how best to organise validation across boundaries. Currently my thinking is keep it very separate with well constructed messages helping]


General
Do not pollute any part of the stack with cross cutting concerns. Use AOP. The entry and exit of a method is usually more than enough context for things like security and logging. If it is not then rethink the size of your methods!
Loosely couple yours stack. Use IoC. A standard IoC container can ease testing and un-clutter the complexity of object creation.

Concerns I need to keep at the back of my mind if we switch to something like the guidelines above:
• Client and Server side Authentication and authorisation. How will we pass security tokens around?
• Concurrency, how will I address 2 user working on the same asset at the same and race conditions?

Bring on the refactoring, I just wish we did it as we went! Please learn from our lessons.

No comments: