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
  • 286
  • 1
  • 8
  • 1
    Does this answer your question? [Are the Repository Pattern and Active Record pattern compatible?](https://softwareengineering.stackexchange.com/questions/284865/are-the-repository-pattern-and-active-record-pattern-compatible) – Ben Cottrell Aug 04 '22 at 17:11
  • @BenCottrell I believe active record is a very distinct pattern from the above. DataRetrieval is done from the active record doesn't it (devil in detail) ? Here I'm simply extending the domain model with persistence, the retrieval is still done from a "collection". – Ced Aug 04 '22 at 17:15
  • 2
    The main component of the Active Record pattern is **the domain model persisting itself.** The Active Record pattern is more than simply querying for data. Persistence (and where this happens) is a central to the Active Record design pattern. – Greg Burghardt Aug 04 '22 at 17:24
  • @GregBurghardt on the other hand none of the drawbacks of active records listed here https://softwareengineering.stackexchange.com/questions/70291/what-are-the-drawbacks-to-the-activerecord-pattern apply in this case. Which is why active records is wildly considered by many, an anti pattern, so calling this pattern active record makes it sound like it shares the same drawbacks – Ced Aug 04 '22 at 17:31
  • I use DI so that I can call SaveChanges in the application service on the same DbContext instance that the repository uses. This does require an ORM that tracks changes. – Rik D Aug 04 '22 at 17:34
  • @RikD calling `saveChanges` does not obey rule 2 in this post. – Ced Aug 04 '22 at 17:35
  • The application service is not *aware* if anything needs to be persisted. That’s what the change tracker of the ORM does. At some point you need to tell the ORM to persist (any possible) changes though, and I don’t see any problem by doing that from the application service. – Rik D Aug 04 '22 at 17:41
  • 2
    I think #2 is a misunderstanding. The application should not care whether each individual entity is persisted, but the application should absolutely be aware of transactional boundaries. This implies the application layer coordinates with the persistence layer on *when* to "save changes", but the application does not know what changes need to be saved. That's what the persistence layer is for. – Greg Burghardt Aug 04 '22 at 17:43
  • @GregBurghardt I understand that the industry as a whole mostly does it this way (and probably for good reasons). However I think there is a point to be made about having the application layer, *totally*, unaware of persistence concerns. Maybe it's not practical which is what I'm trying to find out, so I don't encounter unexpected issues. The "when to save changes" seems to be a persistence concern too. IE: it would do the transaction there. – Ced Aug 04 '22 at 18:14
  • 3
    @Ced: I'm with Greg on this, you are taking a much too literal reading of what it means to not be aware of an item being persisted. Your question acts as if persistence should be a secret that can't be revealed via a method name; which is not the criterion on which to hinge your decision. _"The "when to save changes" seems to be a persistence concern too."_ How are you going to define a transaction in scope of everything the application layer is trying to achieve in a single atomic operation, if not by having the application layer indicate when the atomic operation is complete? – Flater Aug 04 '22 at 18:16
  • 2
    @Ced _"Maybe it's not practical which is what I'm trying to find out,"_ You've responded a few times by excluding practical issues with your approach, such as _"@RikD calling saveChanges does not obey rule 2 in this post."_ Either you're open to alternate points of view or you're not; but it's contradictory to at the same time claim you're trying to find out the practicality and then dismiss more practical approaches as an answer. I think you're being too dogmatic about your understanding of DDD. Purism is hardly ever the right choice in the real world. Reality drives one to make compromises. – Flater Aug 04 '22 at 18:19
  • 1
    @Ced: Just to prove the point, you can have the application layer call an outright `Save` method, which decides _by itself_ whether there is anything to save (e.g. did any value actually get changed? Is this user allowed to make changes? ...) This satisfies the definition of DDD whereby the application layer is not the one making the final decision on whether to save or not; and yet the application layer is still calling a `Save` method. I think you're conflating a suggestive name with an actual technical implementation, but these are two very different things. – Flater Aug 04 '22 at 18:25
  • @Flater I might be taking a too literal approach indeed. I've been reading so many architecture stuff lately and it seems like every thing I do is against a "rule" (self inflicted). My productivity dropped next to zero, which is quite alarming so I'm trying to make more sens of it all by exposing my point of view here. For example, maybe I misunderstood the `saveChanges`: To me it sounded like "update x, then y, then verify result is valid, then persist", which would contradict another ddd view that the application state shouldn't ever be invalid. – Ced Aug 04 '22 at 19:51
  • What I mean is that if the x, y then z thing above holds true then the domain logic should have prevented such a change IE: `bool canExecuteOperationOn(order, tenant, whatever)`. Whether the update is called on an active record or with saveChanges, I don't see how that differs. – Ced Aug 04 '22 at 20:12
  • @Ced: Have you ever seen concept cars? They're bold and really go for a clear and unique style. And by the time the manufacturer needs to build cars that people will actually buy, they're building cars that are significantly simpler with reasonable comforts instead of pure aesthetics, but it will be inspired by that very start concept car that came before it. The same is true of high fashion. It's bold, but the real clothes will be more everyday while retaining the general style. The same is true of software architectures. [..] – Flater Aug 04 '22 at 21:10
  • 1
    @Ced [..] In the real world, you're better off with a watered down version of the concept, which is a trade-off between the core of what makes the concept good, and real-world considerations that enable development at a reasonable pace/budget. I'm not saying there are no pure DDD solutions out there; but I am saying that as you try to approximate pure DDD closer and closer, you will get diminished returns on your invested effort, and it becomes harder to justify to the business that it is necessary to do so. – Flater Aug 04 '22 at 21:12

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

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • 1
    I think flatter had a great point too against this pattern: "How are you going to define a transaction in scope of everything the application layer is trying to achieve in a single atomic operation, if not by having the application layer indicate when the atomic operation is complete?" – Ced Aug 05 '22 at 17:44
  • related https://softwareengineering.stackexchange.com/questions/441348/in-the-oo-ddd-does-the-domain-object-access-the-repository-directly – Ced Oct 01 '22 at 01:13