3

In domain driven design

  1. A repository is a collection like "interface" that hides data source access. It furnish add, remove and retrieval method just like a collection would. It does it using domain language.
  2. The application layer uses the repositories by thinking about them as collection, by retrieving from it or adding items to it, but should not be aware of whether a specific item is being persisted or not.
  3. domain layer should guard domain rules

With those rules in mind: How can entities updates be persisted and should the repository return self persisting entities ?

Example:

Domain

enum Status { awaitingApproval, accepted, declined }

class Order { Status _status; // private Status get status => _status;

// point 3. Protects domain rules set status(Status updatedStatus) { if (status != awaitingApproval) { throw 'cannot update a $status order'; } status = updatedStatus; } }

Application

// aggregate root
class User {
  OrderRepository _ordersRepository;

Future<List<Order>> viewAllOrders() => _ordersRepository.selectAll();

// point 2. the application layer is not aware of whether the order update is persisted or not. Future<void> acceptOrder(Order order) => order status == Status.accepted; }

Repository

class ConcreteOrderRepository {
  Future<List<Order>> selectAll() async => inMemoryStorage.selectAll('orders');
  // point 1. No update method on a collection like object.
}

There is still something missing here: How is the update to the Order being persisted ?

I've seen two version to resolve this architectural issue:

  • add an update method the the repository which has as side effects:
    • You can't think of the repository as a collection, it is now an abstraction over persistence. This is imo a very distinct concept of the original intent of repositories.
    • The application layer is now aware it is using persistence as it has to explicitely call orderRepository.update(order)..
  • The second solution I've seen is to use an ORM framework and adding a bunch of annotation in the domain layer. @Entity() comes to mind. I believe this is the worst solution of the bunch as it polutes the domain layer.

A third solution that I've not seen is to return a PersistedOrder instead of an order to the application layer:


class PersistedOrder extends Order {
  final OrderDataSource orderDataSource;

factory fromOrder(OrderDataSource dataSource, Order order) { return PersistedOrder(dataSource, status: order.status) }

@override status(Status status) { super.status = status; orderDataSource.updateOrder(super.id, this); } }

class ConcreteOrderRepository { final OrderDataSource orderDataSource;

Future<List<Order>> selectAll() async { final allOrders = orderDataSource.selectAll(); return allOrders.map((order) => PersistedOrder.fromOrder(order)); } }

This seems to be in line with point 1, 2 and 3. Only the Persistence layer knows that the Order is in fact a PersistedOrder, other layers just use an order and an orderRepository as a collection where they can retrieve and add elements.

Still, somewhat of a red flag for me is that I've not seen anyone do this, so there might be a reason. Which is what this question is about:

  • Have anyone encountered this pattern ? Does it have a name ?
  • What would be / are possible draw backs here ?
Ced
  • 609

1 Answers1

3

In domain driven design...

I'm with @Flater on this one. DDD is mainly about the domain as ubiquitous language, i.e. used everywhere, including in the code. It is less about these entity-value-repository-service things. The latter are just one interpretation even according to the book. I personally think it is not even a good one at that.

Expressing the domain is highest priority. Not technical categorization and details.

A repository is a collection like "interface" that hides data source access. It furnish add, remove and retrieval method just like a collection would. It does it using domain language.

That is a contradiction. Unless your domain is about CRUD (i.e. you are a database), add/remove/delete/etc. will not be part of the domain language.

An Account does not get "updated". Money gets transferred, it gets closed, etc. These operations imply persistence, but persistence is a technicality underneath.

The application layer uses the repositories by thinking about them as collection, by retrieving from it or adding items to it, but should not be aware of whether a specific item is being persisted or not.

Does that sound like domain language? No it's not. It's technology. Technology should be hidden, "domain" should be visible.

With those rules in mind: How can entities updates be persisted and should the repository return self persisting entities ?

This is a strangely worded question. Should the returned objects be "self-persisting"? This is a technical question, therefore should not significantly influence the design. The real question is, does some use-case that I want to express imply persistence.

If I initiate a cash-transfer, I expect it to be persistent. Probably. If I add two Money objects I don't. Probably, depending on the domain.

You can't think of the repository as a collection, it is now an abstraction over persistence. This is imo a very distinct concept of the original intent of repositories.

I agree, this is not what is implied in the book and it also doesn't make sense. Persistence is a technicality, it is not part of the ubiquitous language (most of the time).

The second solution I've seen is to use an ORM framework and adding a bunch of annotation in the domain layer. @Entity() comes to mind. I believe this is the worst solution of the bunch as it polutes the domain layer.

Agree again, this is pretty bad.

A third solution that I've not seen is to return a PersistedOrder instead of an order to the application layer

As mentioned in the comments, this is somewhat similar to ActiveRecords, but still misses the point entirely.

Having "records" in the first place, that is, bags of data to be retrieved and stored to a database is completely counter to object-orientation and DDD.

Database records are not domain-relevant, not part of the ubiquitous language, therefore it should not be relevant at all for design purposes.

The right alternative is to model the domain. As is. With persistence where it's implied. With UI even, where it's implied. With everything. A conversation with a domain expert / user will not stop at technical boundaries. Technical boundaries do not (again, ymmv) exist in the domain. Users will speak of Account and it's UI interchangeably.

If you're looking for a name on this, it is sometimes called "rich domain". Warning though, name not consistent, sometimes people don't mean the above when invoking rich domain.

HTH