2

Working on a DDD application where I need to persist state. Here's a very simple example:

public interface IRoot {
  public int Id { get; }
  void UpdateValue(int value);
}

public interface IState {
  int Id { get; }
  int CurrentValue { get; }
}

public class AggregateRoot : IRoot {

  private State _state;

  internal AggregateRoot(State state){
    _state = state;
  }

  public int Id { get { return _state.Id; } }

  public void UpdateValue(int value){
    _state.CurrentValue = value;
  }

}

public class State : IState {

  internal State(int id){
    Id = id;
  }

  public int Id { get; internal set; }

  public int CurrentValue { get; internal set; }

}

By using the internal setter / getter only interface, I can ensure that state isn't changed outside of the business logic implemented in the domain root. I have a factory/builder set up within the same project to instantiate new roots w/ state.

My problem comes when it comes to persisting / loading state from some data store. I'm trying to keep the persistence implementation details totally separate from the domain, so I create a repository interface for persisting the state:

public interface IStateRepository {
    void Save(IState state);
    void Delete(int key);
    IState Get(int key);        
}

Since I'm using interfaces without setters in the repository, I can't update the state ID on save, or populate an object directly. The IStateRepository implementation is in a completely separate project, so even id I changed repository to use the state directly, I still wouldn't have access to properties without using reflection.

The only solution I can think of that would still allow me to have clean separation is to implement a DTO that would act as mapping object between the domain and the persistence layers, but that doesn't feel right. I'd essentially be creating a duplicate object with the properties of the State object, but with public setters.

Surely there is some obvious pattern I'm missing?

1
  • What is the advantage of separating state into its own object? It seems this extra layer of encapsulation only serves to create the problem you outline above. Are you creating a translation layer to a legacy system? Jul 19, 2018 at 17:23

1 Answer 1

1

A separated project that implements the IStateRepository is a good way, but you should to consider that you really need to create a internal class on the Data project that implements IState.

Note, since the Interfaces are contracts, imagine if this project is being developing by two people who should to work by respecting these rules, otherwise, there it no way to ensure the able to communicate among these pairs, it means that they just know the definition of IState, but each one should to create a concrete object that ensure these definition.

The only solution I can think of that would still allow me to have clean separation is to implement a DTO that would act as mapping object between the domain and the persistence layers, but that doesn't feel right. I'd essentially be creating a duplicate object with the properties of the State object, but with public setters.

Unfortunately, I think it will be the best solution for you! But, if you create an internal class on the Data project that implements IState, you don't need to map it. Because, these both projects goes to understand the type implicitly.

Anyway, consider that even that both implements IState, it aren't the same things. In conceptual terms, Data Object and Domain Object have different purpose.

Also, I can propose you don't separate the project if your system is not too big, making it, the problem is gone. Usually I do it.

I hope it been useful for you, think about it and good code!

1
  • Good food for thought. I had gone down a similar path before, but it wasn't feeling right and that's because I had the State belonging to the domain, and not to the persistence. In reality, it's a persistence object, that the domain uses. Thanks for your input.
    – aasukisuki
    Jul 19, 2018 at 15:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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