2

Repositories in ddd should give the illusion of an in memory collection.

and

A Repository is essentially a facade for persistence that uses Collection style semantics (Add, Update, Remove) to supply access to data/objects

I've read contradicting examples on what repositories are supposed to be:

abstract class OrderRepository  {
    Order selectOne(String id);
}

or

abstract class OrderRepository  {
    Future<Order> selectOne(String id);
}

or even

abstract class OrderRepository  {
    Stream<Order> selectOne(String id);
}

A return type of Order is not asynchronous, but it can be seen in this microsoft article and many others. The data has to come from a database so how could it return an Order without the DB answer ?

I just need some clarification:

  • Is the "repository" used in two different context one of which would be DDD, where the retrieval is synchronous and another where the repository is used kind of like a DAO ?
  • If indeed by illusion of being in memory it really means synchronous, then how is the data populated inside the repository in the first place ?

3 Answers 3

4

Today, you'd typically make it async. There was a time when mainstream languages and their standard libraries didn't have async/await or things like promises and futures, so you had to do things with threads, and in some circumstances you were even doing blocking calls.

What Eric Evans (author of the original DDD book) meant by "give the illusion of an in memory collection" is, I think, more along the lines of "handle generic data retrieval and management concerns/mechanisms on behalf of domain model objects, so that the domain code wasn't littered with that stuff", rather than "pretend there's no remote database". Cause, you want your domain object to be laser-focused on capturing the business rules. It's about keeping the domain model workable and clean, easy to understand and reason about, but at the same time, you can't ignore the fact that you're making an out-of-process non-local call - you have to balance out the two.

P.S. Regarding "Collection style semantics" - that's one way to do it, but you can also organize the semantics around specific usage scenarios (what Robert Martin would call use cases). You could have multiple repos in your app, backed by the same DB, it doesn't have to be one big God object. DDD doesn't really prescribe all these (tactical) patterns people associate with it, it just gives them as an example of what you could build your system out of. DDD is about how to approach modeling, how to find out what it is that you actually need to build, and how to align your idea of the domain (your conceptual domain model) with the actual needs of the real-world domain. DDD elaborates on this, among other things, in terms of a number of high-level strategic patterns. As long as you do that, you could use a completely different set of tactical patterns, it would still be DDD.

1
3

A repository is just a pattern where you separate the "get object from the storage" (database/file/api/whatever) code from the object.

Typically the storage is a database so there will be some network traffic involved in getting the data.

Anytime you have network traffic you have free CPU cycles as you wait for the network, so an async methodology Task/Future/Promise whatever makes sense.

OK in some situations (testing) the repository might be in memory and not have any need for async, but its a limitation of a strongly typed language and they way people have done async wrappers that forces you to declare the same method signature for all classes that implement the interface

13
  • Realistically the interface should always be returning a Task/Future/Promise/Stream. Since it's an abstraction over the storage mechanism, the synchronousity cannot be assumed, you are not supposed to be aware of the mechanism at that level. Which is why I find it really odd that so many interfaces examples are synchronous.
    – Ced
    Aug 3 at 20:50
  • @Ced: from an application programmer's point of view, I don't think it that's odd. There are thousands of use cases where a synchronous processing fits exactly to the workflow of a user. For example, I enter some data for a new entity, want to store it and get a quick feedback if that was successful or not. Do this asynchronously, and I will be in the middle of entering the data for the next entity when I get a disturbing pop-up that my former storage attempt failed.
    – Doc Brown
    Aug 4 at 7:35
  • @Ced: ... and of course, there are surely also thousands of use cases where async access is useful. Hence, general interfaces should ideally provide both - synchronous and asynchronous methods. But in the repositories of your own application, you can provide whatever suits your application's needs best - synchronous methods, async methods, or both, whatever suits your needs.
    – Doc Brown
    Aug 4 at 7:40
  • 3
    @Ced, it is entirely possible to provide a synchronous interface to an inherently async operation. It just means that whatever waiting needs to happen will happen within the component that implements the interface. Before async operations became widely supported by programming languages, that was actually the norm because it made life so much easier for the users of the interface. Aug 4 at 10:10
  • 3
    @Ced: Which is why I find it really odd that so many interfaces examples are synchronous. Repositories have been around for ages longer than async/await has become commonplace. Today, there are still a lot of developers who don't default to async. This is a matter of either reading documentation that's outdated (pre-async), or outdated developers not accounting for async in their snippets.
    – Flater
    Aug 4 at 11:28
2

Whether you use an Order or a Promise<Order>/Future<Order>/Task<Order> is really more about your program and it's programmers and their familiarity with async/await patterns. Obviously if your program makes use of those, then the async variants are basically mandatory. But if your program aside from your repository is not written with that in mind, it might make more sense to leave it sync for the consistency of the code as a whole.

One pet peeve of mine though: Your method will always return exactly one order. If it's fancy, maybe it will be a nullable Order. Or zero-or-one Order. But it will never return an order... and then another order... and maybe later another order. It just is not a stream. Or an observable. It isn't. So don't make it such. People like working with stream and observables? Great. Let them. In their application code. But your code is an interface, a contract. And a contract should say "I will give you the one order you asked for". If it says "I will give you an undefined number of orders in an undefined time interval" it is just a lie. It won't. You know it, I know it, a bug would be filed if it ever happened, so what does it do there?

So, you can return an Order syncronous or asnycronous that depends on the rest of your program what makes sense. But do not return a stream or observable just because they are hip and you like working with them. Do not lie to your consumers.

1
  • The Stream<Order?> would be somewhere where there is a listener such as a firestore listener, that continuously listens to changes made to that specific order, not a stream of different orders. I was not clear about that and you are right about the nullable.
    – Ced
    Aug 4 at 16:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.