4

I'm reading OO DDD slides from Robert Brautigam, who is quite active here so I hope he can personally answer as I quite agree with his personal understanding of DDD, and approach of having a more object oriented approach instead of a service one.

In our apps we have an awful lot of this:

class OrderService {
   OrderRepository _orderRepository;

Future<void> confirmOrder(Order order) { final confirmedOrder = order.confirm(); // we use immutable entities return _orderRepository.save(confirmedOrder); } }

class DBOrderRepository { Future<void> save(Order order) => db.save(order.toJson()); }

The service seems unnecessary because the domain object, Order in this instance, is doing the work to update the fields it needs to and the service just saves the result. For most use cases where there is only one entity involved it seems like the service layer is very thin and might not need to actually exist at all ? That the call could be made directly inside the domain object, which I think is what OP of the slides alludes at. I understand there is an argument for unit testing each part individually and maybe a single responsability principle, but I'm not convinced those 2 arguments hold.

In slides 31 and 32 he gives 2 options. He eliminates the services, but where the entity is saved to persistence is not clear.

In slide 32:

class Order {

Json toJson(); static Order fromJson();

My problem with this is that implementation details are leaking out of the Order, but most importantly the Order is not persisted. You'd still need a service to call the repository eg the same snippet as I have above (it's the method that we currently use):

class OrderService {
   OrderRepository _orderRepository;

Future<void> confirmOrder(Order order) { final confirmedOrder = order.confirm(); return _orderRepository.save(confirmedOrder); // <== this needs to be called somewhere ?! } }

class DBOrderRepository { Future<void> save(Order order) => db.save(order.toJson()); }

Ced
  • 609

2 Answers2

6

Very short answer: Yeah, sort-of, not quite.

So in OO, objects are not bags of data. So talking about "persisting objects" is already kind-of a non-starter. If I have an object Customer, which has a freezeCreditCards() method, how would I "persist" this object from the outside? What would I even expect this would achieve?

This means the current understanding of what "Repositories" are is already at odds with OO. For several reasons:

  1. The point above about objects not being bags of data.
  2. Even if the object has some internal data that is being changed and even if that data somehow corresponds to some data in the database, there should be no way an external object can just forcefully extract that data from it, be it getters, reflection, or anything else. This would be a complete violation of object boundaries and encapsulation.
  3. Repositories aren't part of the Ubiquitous Language. They are technical artifacts, therefore overhead essentially.
  4. A bit more pragmatically, there is no way our object can change without changing the Repository with it. So things that change together aren't together.

This is what I try to just briefly mention on Slide 29.

Ok, so what now? Well, persistence is a byproduct of a business function. Normally. So when I call freezeCreditCards() I obviously expect that to be permanent/persistent.

But this also means that persistence is not just one thing. One pattern or a generic solution or something like that. Since persistence happens in the context of a business-function, it only makes sense to talk and design persistence in the context of the specific case.

In the slides I list two options that I used in the past:

The toJson() thing is for cases where the requirements demand that we produce some Json to send something over the wire. Integration to other systems. Again, because it is a requirement, it is a business-function, therefore having a public method for it is reasonable.

Again, most of the time persistence is implied. Therefore CRUD methods should not be visible. At all. I give another example I've used in the past, where there is a specific SQL-based implementation of a Customer interface. It fires a direct SQL statement for all behavior it has. I use similar constructs for when behavior needs to go to other services or systems. But again, it is not a generic thing, often it does not fit.

So long answer: The object should do everything to fulfill its implied promises for a given business-function, like persistence. For that, it should talk to any collaborators it has to.

But. These collaborators should not pull data out of our object. That should never happen. So can it have a java.sql.Connection? Sure, why not. Can it have a CustomerDBRepository? No, for the reasons above.

In the talk I also mention that I don't see the point of Services. At all. I went through Vaughn Vernon's Repo to see what he does with Services. I didn't find a single one, where I couldn't find a much better place for it. See example PasswordService. I believe it exists only because "entities" can not be persistent, so they need another "layer" that does it for them. Basically a work-around for a design mistake.

I hope that makes things at least clearer, even if probably not easier.

Update: Under what circumstance would Repositories actually make sense?

If I would have to support multiple databases or had to allow (so the requirements required it) custom implementations, then a Repository-like thing would actually make sense. Note however, that because of the huge costs, I would probably question the need first, before being ok with it :)

1

In general, a DDD repository is the way you retrieve/persist an entity.

If you intend to have the entity itself be able to interact with the repository, it means that each entity will have a reference to the repository object. Plus, all the "persistence things" you can do will have to be exposed through the entity interface, and that smells of SRP violation.

For instance, imagine having to work on many entities massively, maybe update the same property on each. In the way you propose you'll have to do it one by one, which is not ideal from a performance perspective, and you might even consider creating a specific method on the repository if that's a frequent operation. You wouldn't be able to expose that through the entity interface, in a way that makes sense.

You may still want to expose the "save" method, like it's syntactic sugar... but is it worth it? It's probably mostly a matter of taste.

Regarding the dumb service instead, I totally agree that thin abstraction layers should be avoided if possible. Sometimes you're totally sure you are gonna need it (TM), e.g. to coordinate different repositories or carry out some particular logic. But if it's just CRUD, I'm all for using the repos at the application level (e.g. in REST controllers). In case I'll need a "service" layer, I'll add it with 10 minutes of refactoring.

bigstones
  • 806