3

A very simple use case implemented using DDD and java.

I have a FooEntity and a FooRepository. The Entity has a delete method which validates certain state to check whether it is safe to be deleted, and in case this evaluates to true invoke the delete in the repository, which is injected in the entity.

So far so good, but, what happens if somebody invokes the delete method directly in the repository? Then the validation wouldn't be performed.

Placing the validation in the repository would solve the problem, but this would be clearly wrong since it would make necessary to expose the internal state of the entity.

What am I missing?

public class FooEntity {

  @inject
  FooRepository fooRepository;

  private Boolean canBeDeleted;

  public void delete(){
    if (canBeDeleted){
      fooRepository.delete(this);
    }
    throw new CannotBeDeletedException();
  }
}

public class FooRepository {

  @inject
  FooDAO fooDAO;

  public void delete(FooEntity fooEntity){
    fooDAO.delete(fooEntity.getId());
  }
}
diegomtassis
  • 3,557
  • 2
  • 19
  • 29

3 Answers3

3

Don't expose the internal state, expose a method like isDeletable() on the entity. The repository's delete can call entity.isDeletable() before deleting, and raise an exception if you are trying to delete an entity that is not deletable. That way you separate the concerns. The entity has the domain knowledge of it's "deletableness", while the repo knows how to delete the entity.

Rob Conklin
  • 8,806
  • 1
  • 19
  • 23
  • 1
    This is actually the best solution I had come to. The validation logic is encapsulated within the entity and it is not possible to be avoided. What I don't like is that isDeletable() becomes part of the entity's interface and thus the clients may think it is necessary to invoke it before deleting, but it is not. This is a pretty basic scenario and I expected kind of a standard solution. – diegomtassis May 07 '15 at 08:46
0

The example code is fine as is (except that it's strange to have a DAO inside a repository class, as "repository" is just a more abstract name for the same concept as the DAO).

You can't really prevent other developers from calling the wrong methods, except for using static analysis code inspections where available.

The repository should only concern itself with removing the given entity instance from the set of persistent entities. It cannot have logic for checking whether the entity is allowed to be deleted or not, even if the isDeletable() method is in the entity class.

Rogério
  • 16,171
  • 2
  • 50
  • 63
  • 1
    The way I see it the DAO belongs to the infrastructure/persistence layer, its implementation is pretty close to the used database and doesn't know anything about entities, only DTOs. The Repository belongs to the domain layer and returns/handles real entities. I can understand that the DAO doesn't need to check whether it's needed to perform any kind of validation before deleting something, since it is pure infrastructure, it like issuing a delete directly to the db. However the repository belongs to the domain and should be aware of that kind of stuff. – diegomtassis May 07 '15 at 08:21
  • In real-world projects, I normally use JPA/Hibernate, so there would be no point in having both Repositories and DAOs. But note there is no DAO in the context of DDD, only Repositories. And while I agree that a Repository can and should contain "business logic", it should only appear in query DSL expressions (like JPA-QL), not in Java code, since that kind of business logic code belongs to Entities and Domain Services; otherwise, the Repository would end up looking like a Domain Service, if it can contain arbitrary business logic. – Rogério May 07 '15 at 19:27
  • 1
    [The repository speaks the domain language, the DAO does not](http://stackoverflow.com/questions/19935773/dao-repositories-and-services-in-ddd) (it simply works with DTOs, close to the database). The Repository may have injected several DAOs in order to build an aggregate. The DAO is not mentioned in DDD because it is not part of it, it is just a way to implement the repository. If using jpa there is not need of DAOs, true. – diegomtassis May 08 '15 at 07:53
0

I would put the delete functionality in a domain service.

public class FooService {

  @inject
  FooRepository fooRepository;

  public void delete(Foo foo) {

    if( /* insert validation stuff here to check if foo can be deleted */ ) {
      fooRepository.delete(foo);
    }
 }

The way I do it though is I typically use a ValueObject to represent an Entity's identity. E.g.

public class FooId() {

    String foodId;

    public String FooId(String fooId) {
       this.foodId = foodId;
    }
}

public class Foo() {

    FooId id;

    /* other properties */
}

I would then revise FooService to:

public class FooService {

  @inject
  FooRepository fooRepository;

  public void delete(FooId fooId) {

    foo = fooRepository.retrieve(fooId);

    if( /* insert validation stuff here to check if foo can be deleted */ ) {
      fooRepository.delete(foo);
    }
 }

To delete a foo (assuming fooId was passed by a command from the UI:

 fooService.delete(fooId);

I would not inject a FooRepository in an a class that represents entity. I don't think that it is the rightful place. An Entity for me should not be able to create or delete itself. These functions should be in a Domain Service for that Entity.

jett
  • 947
  • 9
  • 27
  • 3
    The domain services goal is to encapsulate business logic which does not belong to entities, this is usually operations that involve more than one entity/value object. Why do you think this delete validation does not belong to the entity? And in such case, since this validation is done based on the state of the entity, it would imply to expose the internals of the entity. – diegomtassis May 08 '15 at 08:26
  • You are right. In that case, inside FooService, you should do a if(foo.canBeDeleted()) { fooRepository.delete(foo); }; It then uses uses the domain to validate if it can be deleted, if it says yes (true), then we call the Repository to delete it. I assumed that you are hard deleting the record. If you are soft deleting it (i.e. just marking the status of the record as deleted without deleting it from the table), you can just call foo.delete() and then call FooRepository to persist the changes. – jett May 11 '15 at 06:24