How to avoid Anemic Domain Models and maintain Separation of Concerns?

前端 未结 4 1522
我在风中等你
我在风中等你 2021-01-30 06:03

It seems that the decision to make your objects fully cognizant of their roles within the system, and still avoid having too many dependencies within the domain model on the dat

4条回答
  •  生来不讨喜
    2021-01-30 06:25

    Really really good question. I have spent quite a bit of time thinking about such topics.

    You demonstrate great insight by noting the tension between an expressive domain model and separation of concerns. This is much like the tension in the question I asked about Tell Don't Ask and Single Responsibility Principle.

    Here is my view on the topic.

    A domain model is anemic because it contains no domain logic. Other objects get and set data using an anemic domain object. What you describe doesn't sound like domain logic to me. It might be, but generally, look-up tables and other technical language is most likely terms that mean something to us but not necessarily anything to the customers. If this is incorrect, please clarify.

    Anyway, the construction and persistence of domain objects shouldn't be contained in the domain objects themselves because that isn't domain logic.

    So to answer the question, no, you shouldn't inject a whole bunch of non-domain objects/concepts like lookup tables and other infrastructure details. This is a leak of one concern into another. The Factory and Repository patterns from Domain-Driven Design are best suited to keep these concerns apart from the domain model itself.

    But note that if you don't have any domain logic, then you will end up with anemic domain objects, i.e. bags of brainless getters and setters, which is how some shops claim to do SOA / service layers.

    So how do you get the best of both worlds? How do you focus your domain objects only domain logic, while keeping UI, construction, persistence, etc. out of the way? I recommend you use a technique like Double Dispatch, or some form of restricted method access.

    Here's an example of Double Dispatch. Say you have this line of code:

    entity.saveIn(repository);
    

    In your question, saveIn() would have all sorts of knowledge about the data layer. Using Double Dispatch, saveIn() does this:

    repository.saveEntity(this.foo, this.bar, this.baz);
    

    And the saveEntity() method of the repository has all of the knowledge of how to save in the data layer, as it should.

    In addition to this setup, you could have:

    repository.save(entity);
    

    which just calls

    entity.saveIn(this);
    

    I re-read this and I notice that the entity is still thin because it is simply dispatching its persistence to the repository. But in this case, the entity is supposed to be thin because you didn't describe any other domain logic. In this situation, you could say "screw Double Dispatch, give me accessors."

    And yeah, you could, but IMO it exposes too much of how your entity is implemented, and those accessors are distractions from domain logic. I think the only class that should have gets and sets is a class whose name ends in "Accessor".

    I'll wrap this up soon. Personally, I don't write my entities with saveIn() methods, because I think even just having a saveIn() method tends to litter the domain object with distractions. I use either the friend class pattern, package-private access, or possibly the Builder pattern.

    OK, I'm done. As I said, I've obsessed on this topic quite a bit.

提交回复
热议问题