views:

1018

answers:

4

I've read some of the questions regarding anemic domain models and separation of concerns. What are the best techniques for performing/attaching domain logic on anemic domain objects? At my job, we have a pretty anemic model, and we're currently using "helper" classes to perform the database/business logic on the domain objects. For example:

public class Customer
{
    public string Name {get;set;}
    public string Address {get;set;}
}

public class Product
{
    public string Name {get;set;}
    public decimal Price {get;set;}
}

public class StoreHelper
{
    public void PurchaseProduct(Customer c, Product p)
    {
         // Lookup Customer and Product in db
         // Create records for purchase
         // etc.
    }
}

When the app needs to do a purchase, it would create the StoreHelper, and call the method on the domain objects. To me, it would make sense for the Customer/Product to know how to save itself to a repository, but you probably wouldn't want Save() methods on the domain objects. It would also make sense for a method like Customer.Purchase(Product), but that is putting domain logic on the entity.

Here are some techniques I've come across, not sure which are good/bad:

  1. Customer and Product inherit from an "Entity" class, which provides the basic CRUD operations in a generic fashion (using an ORM maybe).
    • Pros: Each data object would automatically get the CRUD operations, but are then tied to the database/ORM
    • Cons: This does not solve the problem of business operations on the objects, and also ties all domain objects to a base Entity that might not be appropriate
  2. Use helper classes to handle the CRUD operations and business logic
    • Does it make sense to have DAOs for the "pure database" operations, and separate business helpers for the more business-specific operations?
    • Is it better to use non-static or static helper classes for this?
    • Pros: domain objects are not tied to any database/business logic (completely anemic)
    • Cons: not very OO, not very natural to use helpers in application code (looks like C code)
  3. Use the Double Dispatch technique where the entity has methods to save to an arbitrary repository
    • Pros: better separation of concerns
    • Cons: entities have some extra logic attached (although it's decoupled)
  4. In C# 3.0, you could use extension methods to attach the CRUD/business methods to a domain object without touching it
    • Is this a valid approach? What are pros/cons?
  5. Other techniques?

What are the best techniques for handling this? I'm pretty new to DDD (I'm reading the Evans book - so maybe that will open my eyes)

+1  A: 

I've always thought of the anemic domain model as an anti pattern. It's clear that a customer will purchase products, that ability can be generised by an interface implementation

Interface IPurchase
      Purchase(Product);

, so a any of your domain objects can then implement that as required. In that way you can introduce functionality to your domain objects - which is exactly where it should be.

MrTelly
+5  A: 

Martin Fowler has written a lot about domain models, including anemic domain models. He also has brief descriptions (and UML class diagrams) of many design patterns for domain models and databases that might be helpful: Catalog of "Patterns of Enterprise Application Architecture".

I would suggest looking at the Active Record and Data Mapper patterns. From the description of your problem, it sounds like your helper classes contain both domain/business rules and database implementation details.

The Active Record would move the helper's domain logic and database code into the other domain objects (like your Entity base class). The Data Mapper would move the helper's domain logic into the domain objects and the database code into a separate map object. Either approach would be more object-oriented than procedural-style helper classes.

Eric Evans' "Domain Driven Design" book is excellent. It gets a bit dry, but is definitely worth it. InfoQ has a "Domain Driven Design Quickly" mini-book that is a good introduction to Evans' book. Plus "Domain Driven Design Quickly" is available as a free PDF.

cpeterso
+4  A: 

In order to avoid anemic model, refactor your helper classes:

Logic like:
"Customer.PurchaseProduct(Product product, Payment payment)",
"Customer.KillCustomer(Person killer, Weapon weapon)"
should exist right into "Customer" domain object.

Logic like:
"Customer.IsCustomerAlive()"
"Customer.IsCustomerHappy()"
should go to specifications.

Logic like:
"Customer.Create()",
"Customer.Update()"
obviously should go to repositories.

Logic like:
"Customer.SerializeInXml()"
"Customer.GetSerializedCustomerSizeInBytes()"
should go to services.

Complex constructors should go to factories.

That's how i see it. I would be glad if someone could comment my understanding of DDD approach.


Edit:

Sometimes - anemic domain model shouldn't be avoided.

Edited my answer to add that DDD isn't about picking up and dropping patterns.
DDD is about way we think.

Arnis L.
Seems like a LOT of different classes just to handle a customer. Why not throw most of it in a single class, with a service to handle anything complex?
Jess
My answer is old like hell. :D
Arnis L.
@LuckyLindy Mainly because DDD is about creating bridge between domain experts and programmers. Domain model shouldn't contain technical stuff, otherwise ubiquitous language won't be able to exist. To move out technical stuff - we must abstract it. Abstracting something always inflates code base.
Arnis L.
Damn, it still gets upvoted. Answer sounds as if it was easy to mechanically avoid anemic domain model. Unfortunately - that's not true.
Arnis L.
A: 

One approach that you haven't mentioned is using AOP to handle your data access. An example of my recent use of this approach (though vastly simplified for posting purposes) was that I had an Account domain entity which had a debit method, encapsulating the business logic required in order to make a successful debit from the account.

N.B. All code is Java with AspectJ AOP notation...

public boolean debit(int amount) {
 if (balance - amount >= 0) {
  balance = balance - amount;
  return true;
 }
 return false;
}

With the appropriate repository injected into my aspect, I then used a pointcut to intercept calls to this method...

pointcut debit(Account account,int amount) :
 execution(boolean Account.debit(int)) &&
 args(amount) &&
 target(account);

...and applied some advice:

after(Account account, int amount) returning (boolean result)  : debit(account,amount) {
 if (result) getAccountRepository().debit(account, amount);
}

In my opinion this gives a nice separation of concerns, and allows your domain entities to focus entirely on the business logic of your application.

Chris R