In domain driven design
- 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.
- 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.
- 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 ?