Single Responsibility Principle vs Anemic Domain Model anti-pattern
Asked Answered
T

7

63

I'm in a project that takes the Single Responsibility Principle pretty seriously. We have a lot of small classes and things are quite simple. However, we have an anemic domain model - there is no behaviour in any of our model classes, they are just property bags. This isn't a complaint about our design - it actually seems to work quite well

During design reviews, SRP is brought out whenever new behaviour is added to the system, and so new behaviour typically ends up in a new class. This keeps things very easily unit testable, but I am perplexed sometimes because it feels like pulling behaviour out of the place where it's relevant.

I'm trying to improve my understanding of how to apply SRP properly. It seems to me that SRP is in opposition to adding business modelling behaviour that shares the same context to one object, because the object inevitably ends up either doing more than one related thing, or doing one thing but knowing multiple business rules that change the shape of its outputs.

If that is so, then it feels like the end result is an Anemic Domain Model, which is certainly the case in our project. Yet the Anemic Domain Model is an anti-pattern.

Can these two ideas coexist?

EDIT: A couple of context related links:

SRP - http://www.objectmentor.com/resources/articles/srp.pdf
Anemic Domain Model - http://martinfowler.com/bliki/AnemicDomainModel.html

I'm not the kind of developer who just likes to find a prophet and follow what they say as gospel. So I don't provide links to these as a way of stating "these are the rules", just as a source of definition of the two concepts.

Toler answered 9/9, 2009 at 11:10 Comment(6)
Have you learned much since you posted this? I have a couple years dev experience under my belt, and I'm in a similar situation you described where I see the project showing signs of the Anemic Domain model but I'm not sure how to balance OOD with SRP.Bilingual
Wow, it's been a while. I think this reflects where OO has gone - a long way from where it started. At college (ages ago), I learned OO was about encapsulating data with related actions in classes, and behavior sharing via inheritance. Now it's lightweight data classes with other classes to act on them, glued together via design patterns and dependency injection, and inheritance is a four letter word. The SRP works much better with the latter style of OO than the former.Toler
What have I learned? The original idea of modelling entities as rich classes can lead to a mess of hierarchies and holes punched into encapsulation to allow the model to flex in new ways. It doesn't work well with TDD, which partly drove SRP and DI. Mostly it's fine. We've lost having intuitive places in code where you can lookup how your system makes certain functionality work. Now you have to know all the actor classes involved or scan the code base. We rely on IDEs to find usages, implementations of interfaces, etc. These are partly ways of coping with what SRP and DI have brought us.Toler
At a high level, in moving from rich domain models towards SRP, we've swapped clean models that evolve into monsters that are hard to split up for clean sets of small classes that eventually evolve into a mess no one can fully track. The rich domain models are tempting, especially early on, but ultimately you find you can never perfectly model the problem, and it can go bad quickly. The SRP approach is probably more flexible in the long run, but neither will grant you life long happiness.Toler
Thanks for getting back so quickly. Sounds like there's no silver bullet. I appreciate the trade offs you point out.Bilingual
Yeah, there never is a silver bullet. I'd add this, though: If you're going with SRP, work hard to keep the design patterns comprehensible, and collapse redundant layers / classes when they're no longer needed, or too confusing to work out how they interact. If you're going with rich domain model classes, make sure to test them, to force the behavior contained within to be driven by the public interface. If it doesn't work well to make it public for testing, that's probably a good time to create a new class.Toler
A
9

I'd have to say "yes", but you have to do your SRP properly. If the same operation applies to only one class, it belongs in that class, wouldn't you say? How about if the same operation applies to multiple classes? In that case, if you want to follow the OO model of combining data and behavior, you'd put the operation into a base class, no?

I suspect that from your description, you're ending up with classes which are basically bags of operations, so you've essentially recreated the C-style of coding: structs and modules.

From the linked SRP paper: "The SRP is one of the simplest of the principle, and one of the hardest to get right."

Affiliation answered 9/9, 2009 at 11:42 Comment(6)
Hmm... I would like to be convinced, but I am not. Favor composition over inheritance speaks against your argument...Muezzin
Okay, we can trade scripture quotes, or wrangle about whether "favor" requires absolutism. Or we can try to solve the OP's problem. What's your thought on how to resolve the OP's issue?Affiliation
Thanks for your answer. I am finding these days that the OO model of combining data and behaviour is very out of fashion. I'm certainly aware of the pitfalls that can come with complex inheritance hierarchies but it seems that inheritance is almost a taboo word these days. SRP and favour composition over inheritance seem to push against rich domain models. I think the problems come when principles are followed because they're principles, rather than because they work best for your project. As you say, scripture quotes.Toler
Your penultimate sentence is very wise, but it will put you at odds with a lot of people. Ideology is easier than analysis.Affiliation
@CPerkins: I've been following this question because I am genuinely interested in getting an answer myself - I have much the same experience that composition over inheritance tends to make Domain Models more anemic. It's just that composition has a lot of other advantages, so I am reluctant to model logic by inheritance unless it makes sense. If I had had a better answer myself, I would have posted it. I would have loved to get a convincing answer, but I guess that I will need to keep exploring this professionally myself. It has nothing to do with 'scripture'.Muezzin
@Mark - fair enough. One of the challenges we face is that the best practices aren't consistent. It's as though we were all teaching ourselves to drive, and learning and teaching practices like: "don't go too fast", and "don't go too slow"... with a general lack of knowledge about what constitutes the right speed for the situation. My apologies for assuming that your simple mention of the Bloch tip was ideological - truly, I run into that more often than I'd hope, but that's no excuse for thinking it of you.Affiliation
A
14

Rich Domain Model (RDM) and Single Responsibility Principle (SRP) are not necessarily at odds. RDM is more at odds with a very specialised subclassof SRP - the model advocating "data beans + all business logic in controller classes" (DBABLICC).

If you read Martin's SRP chapter, you'll see his modem example is entirely in the domain layer, but abstracting the DataChannel and Connection concepts as separate classes. He keeps the Modem itself as a wrapper, since that is useful abstraction for client code. It's much more about proper (re)factoring than mere layering. Cohesion and coupling are still the base principles of design.

Finally, three issues:

  • As Martin notes himself, it's not always easy to see the different 'reasons for change'. The very concepts of YAGNI, Agile, etc. discourage the anticipation of future reasons for change, so we shouldn't invent ones where they aren't immediately obvious. I see 'premature, anticipated reasons for change' as a real risk in applying SRP and should be managed by the developer.

  • Further to the previous, even correct (but unnecessary anal) application of SRP may result in unwanted complexity. Always think about the next poor sod who has to maintain your class: will the diligent abstraction of trivial behaviour into its own interfaces, base classes and one-line implementations really aid his understanding of what should simply have been a single class?

  • Software design is often about getting the best compromise between competing forces. For example, a layered architecture is mostly a good application of SRP, but what about the fact that, for example, the change of a property of a business class from, say, a boolean to an enum has a ripple effect across all the layers - from db through domain, facades, web service, to GUI? Does this point to bad design? Not necessarily: it points to the fact that your design favours one aspect of change to another.

Austronesia answered 3/6, 2010 at 8:28 Comment(1)
"DBABLICC" is not really a form of SRP but more of a way for procedural programmers to rationalise their procedural programming using OOP language.Hepta
A
9

I'd have to say "yes", but you have to do your SRP properly. If the same operation applies to only one class, it belongs in that class, wouldn't you say? How about if the same operation applies to multiple classes? In that case, if you want to follow the OO model of combining data and behavior, you'd put the operation into a base class, no?

I suspect that from your description, you're ending up with classes which are basically bags of operations, so you've essentially recreated the C-style of coding: structs and modules.

From the linked SRP paper: "The SRP is one of the simplest of the principle, and one of the hardest to get right."

Affiliation answered 9/9, 2009 at 11:42 Comment(6)
Hmm... I would like to be convinced, but I am not. Favor composition over inheritance speaks against your argument...Muezzin
Okay, we can trade scripture quotes, or wrangle about whether "favor" requires absolutism. Or we can try to solve the OP's problem. What's your thought on how to resolve the OP's issue?Affiliation
Thanks for your answer. I am finding these days that the OO model of combining data and behaviour is very out of fashion. I'm certainly aware of the pitfalls that can come with complex inheritance hierarchies but it seems that inheritance is almost a taboo word these days. SRP and favour composition over inheritance seem to push against rich domain models. I think the problems come when principles are followed because they're principles, rather than because they work best for your project. As you say, scripture quotes.Toler
Your penultimate sentence is very wise, but it will put you at odds with a lot of people. Ideology is easier than analysis.Affiliation
@CPerkins: I've been following this question because I am genuinely interested in getting an answer myself - I have much the same experience that composition over inheritance tends to make Domain Models more anemic. It's just that composition has a lot of other advantages, so I am reluctant to model logic by inheritance unless it makes sense. If I had had a better answer myself, I would have posted it. I would have loved to get a convincing answer, but I guess that I will need to keep exploring this professionally myself. It has nothing to do with 'scripture'.Muezzin
@Mark - fair enough. One of the challenges we face is that the best practices aren't consistent. It's as though we were all teaching ourselves to drive, and learning and teaching practices like: "don't go too fast", and "don't go too slow"... with a general lack of knowledge about what constitutes the right speed for the situation. My apologies for assuming that your simple mention of the Bloch tip was ideological - truly, I run into that more often than I'd hope, but that's no excuse for thinking it of you.Affiliation
H
7

The quote from the SRP paper is very correct; SRP is hard to get right. This one and OCP are the two elements of SOLID that simply must be relaxed to at least some degree in order to actually get a project done. Overzealous application of either will very quickly produce ravioli code.

SRP can indeed be taken to ridiculous lengths, if the "reasons for change" are too specific. Even a POCO/POJO "data bag" can be thought of as violating SRP, if you consider the type of a field changing as a "change". You'd think common sense would tell you that a field's type changing is a necessary allowance for "change", but I've seen domain layers with wrappers for built-in value types; a hell that makes ADM look like Utopia.

It's often good to ground yourself with some realistic goal, based on readability or a desired cohesion level. When you say, "I want this class to do one thing", it should have no more or less than what is necessary to do it. You can maintain at least procedural cohesion with this basic philosophy. "I want this class to maintain all the data for an invoice" will generally allow SOME business logic, even summing subtotals or calculating sales tax, based on the object's responsibility to know how to give you an accurate, internally-consistent value for any field it contains.

I personally do not have a big problem with a "lightweight" domain. Just having the one role of being the "data expert" makes the domain object the keeper of every field/property pertinent to the class, as well as all calculated field logic, any explicit/implicit data type conversions, and possibly the simpler validation rules (i.e. required fields, value limits, things that would break the instance internally if allowed). If a calculation algorithm, perhaps for a weighted or rolling average, is likely to change, encapsulate the algorithm and refer to it in the calculated field (that's just good OCP/PV).

I don't consider such a domain object to be "anemic". My perception of that term is a "data bag", a collection of fields that has no concept whatsoever of the outside world or even the relation between its fields other than that it contains them. I've seen that too, and it's not fun tracking down inconsistencies in object state that the object never knew was a problem. Overzealous SRP will lead to this by stating that a data object is not responsible for any business logic, but common sense would generally intervene first and say that the object, as the data expert, must be responsible for maintaining a consistent internal state.

Again, personal opinion, I prefer the Repository pattern to Active Record. One object, with one responsibility, and very little if anything else in the system above that layer has to know anything about how it works. Active Record requires the domain layer to know at least some specific details about the persistence method or framework (whether that be the names of stored procedures used to read/write each class, framework-specific object references, or attributes decorating the fields with ORM information), and thus injects a second reason to change into every domain class by default.

My $0.02.

Harrisharrisburg answered 31/8, 2010 at 15:44 Comment(1)
I think your comment near the end that the object must be responsible for maintaining a consistent internal state is the real clincher. Anaemic models I've seen leave this to classes that manipulate the model through public setters, based on the idea that calculating a value is a different responsibility to providing the value, regardless of the complexity of the calculation.Toler
S
5

I've found following the solid principles did in fact lead me away from DDD's rich domain model, in the end, I found I didn't care. More to the point, I found that the logical concept of a domain model, and a class in whatever language weren't mapped 1:1, unless we were talking about a facade of some sort.

I wouldn't say this is exactly a c-style of programming where you have structs and modules, but rather you'll probably end up with something more functional, I realise the styles are similar, but the details make a big difference. I found my class instances end up behaving like higher order functions, partial functions application, lazily evaluated functions, or some combination of the above. It's somewhat ineffable for me, but that's the feeling I get from writing code following TDD + SOLID, it ended up behaving like a hybrid OO/Functional style.

As for inheritance being a bad word, i think that's more due to the fact that the inheritance isn't sufficiently fine grained enough in languages like Java/C#. In other languages, it's less of an issue, and more useful.

Schnitzel answered 14/9, 2009 at 20:38 Comment(0)
S
1

I like the definition of SRP as:

"A class has only one business reason to change"

So, as long as behaviours can be grouped into single "business reasons" then there is no reason for them not to co-exist in the same class. Of course, what defines a "business reason" is open to debate (and should be debated by all stakeholders).

Secede answered 29/10, 2009 at 20:12 Comment(0)
T
1

Before I get into my rant, here's my opinion in a nutshell: somewhere everything has got to come together... and then a river runs through it.

I am haunted by coding.

=======

Anemic data model and me... well, we pal around a lot. Maybe it's just the nature of small to medium sized applications with very little business logic built into them. Maybe I am just a bit 'tarded.

However, here's my 2 cents:

Couldn't you just factor out the code in the entities and tie it up to an interface?

public class Object1
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    private IAction1 action1;

    public Object1(IAction1 action1)
    {
        this.action1 = action1;
    }

    public void DoAction1()
    {
        action1.Do(Property1);
    }
}

public interface IAction1
{
    void Do(string input1);
}

Does this somehow violate the principles of SRP?

Furthermore, isn't having a bunch of classes sitting around not tied to each other by anything but the consuming code actually a larger violation of SRP, but pushed up a layer?

Imagine the guy writing the client code sitting there trying to figure out how to do something related to Object1. If he has to work with your model he will be working with Object1, the data bag, and a bunch of 'services' each with a single responsibility. It'll be his job to make sure all those things interact properly. So now his code becomes a transaction script, and that script will itself contain every responsibility necessary to properly complete that particular transaction (or unit of work).

Furthermore, you could say, "no brah, all he needs to do is access the service layer. It's like Object1Service.DoActionX(Object1). Piece of cake." Well then, where's the logic now? All in that one method? Your still just pushing code around, and no matter what, you'll end up with data and the logic being separated.

So in this scenario, why not expose to the client code that particular Object1Service and have it's DoActionX() basically just be another hook for your domain model? By this I mean:

public class Object1Service
{
    private Object1Repository repository;

    public  Object1Service(Object1Repository repository)
    {
        this.repository = repository;
    }

    // Tie in your Unit of Work Aspect'ing stuff or whatever if need be
    public void DoAction1(Object1DTO object1DTO)
    {
        Object1 object1 = repository.GetById(object1DTO.Id);
        object1.DoAction1();
        repository.Save(object1);
    }
}

You still have factored out the actual code for Action1 from Object1 but for all intensive purposes, have a non-anemic Object1.

Say you need Action1 to represent 2 (or more) different operations that you would like to make atomic and separated into their own classes. Just create an interface for each atomic operation and hook it up inside of DoAction1.

That's how I might approach this situation. But then again, I don't really know what SRP is all about.

Thready answered 14/3, 2012 at 23:6 Comment(0)
A
0

Convert your plain domain objects to ActiveRecord pattern with a common base class to all domain objects. Put common behaviour in the base class and override the behaviour in derived classes wherever necessary or define the new behaviour wherever required.

Anorthosite answered 3/6, 2010 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.