1

I am looking into improving my overall application architecture and (I think) I understand the issues my Anemic Models are causing.

Here is my current architecture:

  • Controller with injected Service and Controller Action accepting a DTO
  • Service with injected repository. Service function takes DTO, calls repository, does whatever it needs to do with the entity (lets say update properties on the entity) and saves it.

What's wrong with above:

In this setup, all Entity properties have Public getters and setters, there is no encapsulation, no OOP, no code re-usability. When I need to update this entity in another Service (or any other method for that matter), I have to write the same code again.

How to fix this:

Obviously this is not ideal. I've decided it would make sense to make the Entity Rich, by adding an Update method to allow me to reuse my code throughout all my services.

Confusion:

Where I am confused is,

  • How does my Rich Entity communicate with the Service?
  • Should my Update method in the Entity take the IEntityRepository as parameter so I can call repository.Save() in the Entity?
  • Should repository.Save() be called in the Service?
  • What if I need to call an Http API from the service because of some specific action happening in the Update in the Entity? Should I call the Http API from the Entity Update() method?

Thanks tons for any directions on how to deal with this.

  • "I have to write the same code again." What code is re-written? I'm imagining something like user.email = 'new@email.com', then userRepository.save(user). – bitsoflogic Jan 31 at 16:43
  • @bitsoflgic Pretty much yes. But it can get a lot more complicated than that. For example, based on the presence or change of the email I might have to call one of my microservices to do an update. Or trigger a background job. Or wipe my redis cache. Or update some other entities etc. etc. – bobek Jan 31 at 16:45
  • 1
    If those details need to be triggered every time the email is changed, I would expect the userRepository to be responsible for setting that process in motion. Because that's when the change is actually happening. – bitsoflogic Jan 31 at 17:03
  • @bitsoflogic Should the repository be a persistence layer only? Save, Retrieve, Update, Delete the entities? Are you suggesting that I pass the IHttpInvoker to the Save() of Repository? – bobek Jan 31 at 17:45
4

In this setup, all Entity properties have Public getters and setters, there is no encapsulation, no OOP, no code re-usability. When I need to update this entity in another Service (or any other method for that matter), I have to write the same code again.

Yup - that's a completely normal starting point.

If you squint, you can see that the database sort of looks like an anemic object. You pull (get) some value from it, do a bunch of work to compute a new value, and write (set) the new value into the store.

oldValue = db.get(id)
newValue = compute(oldValue)
db.set(id, newValue)

The pattern might look more familiar if we change the spelling slightly:

oldValue = db.get(id)

anemicModel = orm.entity(id, oldValue)
// compute
{
    anemicModel.set()
}
newValue = orm.value(anemicModel)

db.set(id, newValue)

Using a rich domain model introduces a few small changes:

oldValue = db.get(id)

richModel = factory.model(id, oldValue)
{
    // tell, don't ask!
    richModel.compute()
}

newValue = richModel.value()

db.set(id, newValue)

The main point being that you can keep all of the business code together in the implementation of RichModel, without needing to think about how persistence works.

Usually, the persistence concerns are hidden away by a repository abstraction, so the controller may instead look like

richModel = repo.get(id)
richModel.compute()
repo.store(id, richModel)

Should my Update method in the Entity take the IEntityRepository as parameter so I can call repository.Save() in the Entity?

No -- the point of having the repository is to separate persistence concerns from domain concerns. It's not the model's job to save itself.

What if I need to call an Http API from the service because of some specific action happening in the Update in the Entity? Should I call the Http API from the Entity Update() method?

If the rich model needs to send message out, it usually does so via a Domain Service; you pass in some domain specific spelling of a capability, which the rich model can invoke as necessary, and in your application code you choose which implementation of that capability to use.

(In other words, the interface for the domain service is defined in your domain module, but the implementations are provided by your application or infrastructure modules).

3

You are on the right track!

To help us understand a clean architecture, let us first breakdown the "flow" of your service layer and engage in a brief summary of where your current application has gone wrong.

At a high level, pretty much every application follows these three steps:

  1. Retrieve entities (state) from storage
  2. Coordinate entities (mutate state)
  3. Persist entities (state)

The critical step is number 2. It is in this step where you have realized duplication is occurring in your application. This is because you are doing more than just coordination in your controllers (services). That is, you are actually carrying out validation/mutation as well. As you have astutely pointed out this is making your validation/mutation difficult to reuse (because steps 1 and 3 are occurring on either side of it), and that this can be solved by isolating (encapsulating) the validation/mutation.

What this "looks" like in your application is your service layer "telling" your model to perform some change (User.ChangeEmail(email)) rather than becoming embroiled in the details of "how" some change must occur.

Your entities should be completely unaware of the world around them. An entity's sole responsibility is to enforce/apply mutations. Your service will persist any changes that may have occurred when it gets to step 3 (e.g. users.Save(user)).

Side effects are best-implemented using events. For example ChangeEmail above might raise an event to indicate an email has changed. This event is caught by your service layer where it can begin it's 3-step process all over again or dispatch some other action (say send an email to the new address).

1
  1. Your entity should not communicate with your service, in the same way that your service does not communicate with your controller. The communication flows down through the layers, not up. The service can act according to the result of method calls on the entity. For more complicated use cases, the entity may need to return some kind of enumeration or result type.
  2. Most people would argue no, the entity should not be responsible for saving itself. Saving is not really part of the domain model; your entity should theoretically be reusable in another application that doesn't use a database (or other repository). So yes, most would say that repository.Save() should be called from within the service.
  3. As briefly mentioned in (1), my preferred solution here would involve the Update() method returning a result type (look up algebraic data types in your language of choice). The service would then be responsible for orchestration: knowing that if a certain result is returned, it needs to call an external API. Alternatively, an interface to the API could be passed as a parameter to the Update() method. This would still decouple the domain model from technical concerns (i.e. HTTP).
  • Thanks for the answer. I've been trying out both solutions, passing in the interface to the API to the method as parameter in some spots. In others, I return something from the Entity method to tell the service what to do next. However, if I don't pass in the API interface every time I call the Update on the Entity, I have to remember to do all this extra logic with checking the result and calling the Http Request. – bobek Jan 31 at 17:01
1

It's not the Update() method you want to put on your entity to move to a rich model. It's the services method.

Your controller method would change as follows:

#ADM
public void UpdateAddress(string customerId, Address data)
{
   var c = this.repo.LoadCustomer(customerId);
   this.customerAddressService.Update(c, data)
   this.repo.SaveCustomer(c);
}

#Classic OOP
public void UpdateAddress(string customerId, Address data)
{
   var c = this.repo.LoadCustomer(customerId);
   c.UpdateAddress(data);
   this.repo.SaveCustomer(c);
}

The danger you are skirting around with Customer.Update() is the active record pattern, where the logic to save the Customer is built into Customer.

You can get around this as you say by injecting a repository

public class Customer
{
    public void Update()
    {
        this.repository.Save(this.propertiesAsDtoObject)
    }
}

But now its not really any different from your previous:

public class Service
{
    public void Update(Customer c)
    {
        this.repository.Save(c)
    }
}
  • My architecture is a little different. The controller calls the service method. The service calls the repository. But I think this makes sense still. My service (your controller) calls the repository to get the entity. Then entity.Update(). then repository.Save(). My question is, what if the Update() has to call an HttpMethod. should I pass in the IHttpInvoker to the entity.Update()? – bobek Jan 31 at 16:58
  • no you would pass in a repository which saved to an api instead of a db – Ewan Jan 31 at 18:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.