We were working through the following iterations of an exercise:
First iteration
A user can register with a user name.
For instance: @foolano
If anyone else has already registered with that name there is an error.
Second iteration
A user can follow another users.
To do so it's only required to know the user name of the user to be followed.
Anyone can query the followers of any given user just knowing its user name.
Third iteration
The registered users and their followers must be persisted.
(source: Diseño modular dirigido por pruebas workshop at PyConEs 2014)
We produced several application services for this features that at some point collaborated with a users repository that we hadn't yet created so we mocked it in their specs.
In these tests, every time we allow or expect a method call on our repository double, we are defining not only the messages that the users repository can respond to (its public interface) but also what its clients expect from each of those messages, i.e. its contract.
In other words, at the same time we were testing the application services, we defined from the point of view of its clients the responsibilities that the users repository should be accountable for.
The users repository is at the boundary of our domain.
It's a port that allows us to not have to know anything about how users are stored, found, etc. This way we are able to just focus on what its clients want it to do for them, i.e., its responsibilities.
This results in more stable interfaces. As I heard Sandi Metz say once:
"You can trade the unpredictability of what others do for the constancy of what you want."
which is a very nice way to explain the "Program to an interface, not an implementation" design principle.
How those responsibilities are carried out is something that each different implementation (or adapter) of the users repository must be responsible of.
However, the terms of the contract that its clients rely on, must be respected by all of the adapters.
In this sense, any adapter must be substitutable by any other without the clients being affected, (yes, you're right, it's the Liskov substitution principle).
The only way to ensure this substitutability is by testing each new adapter to see if it also respects the terms of the contract.
This is related to J. B. Rainsberger's idea of contract tests mentioned in his Integrated Tests Are A Scam talk and in his great TDD course, and also to Jason Gorman's idea of polymorphic testing.
Ok, but how can we test that all the possible implementations of the user repository respect the contract without repeating a bunch of tests?
This is one way to do it in Ruby using RSpec.
We created a RSpec shared example in a file named users_repository_contract.rb where we wrote the tests that characterize the behavior that users repository clients were relying on:
Then for each implementation of the users repository you just need to include the contract using RSpec it_behaves_like method, as shown in the following two implementations:
You could still add any other test that only had to do with a given implementation in its spec.
This solution is both very readable and reduces a lot of duplication in the tests.
However, the idea of contract tests is not only important from the point of view of testing.
In dynamic languages, such as Ruby, they also serve as a mean to highlight and document the role of duck types that might otherwise go unnoticed.
Hi Manuel, very interesting post. The step by step approach is very instructive :)
ReplyDeleteJust one question about layers structure: In the `in_memory_users_repository_spec.rb` we can see how you require the repository contract: `require_relative '../cos/infrastructure/users_repository_contract'`. In this path there's an "infrastructure" folder and I'm wondering if you consider this kind of contracts as an infrastructure thing and why or if it's just a typo or I've misunderstood it.
As far as I'm concerned, they should live inside the domain (only the class contract, not the different implementations) because they talk about things inside our domain (just what you're talking about in this post: a message with a specific header and a return value).
It's also explained in the post you've already linked by Lev Gorodinski (http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/) as: "A repository implementation is also an example of an infrastructural service. The interface is declared in the domain layer and is an important aspect of the domain. However, the specifics of the communication with durable storage mechanisms are handled in the infrastructure layer"
Thanks for your time and see you!
Hi Javier,
DeleteI consider users_repository_contract part of the core specs.
I wanted to have a folder inside cos (the core) in which to collect the contracts of all the repositories and I chose to name it "infrastructure".
You are right, it's a misleading name. I'll rename it to "repositories_contracts".
Thanks for the feedback.
Best regards,
M
Oooookok, no worries Manuel :)
DeleteI'm used to name it as "Repositories" (without the "Contracts" suffix) because I understand that if they're inside the domain folder, they aren't concrete implementations but contracts. Then, I would create a separate "Infrastructure" folder which contains the "MySQL" one (for instance) with the Repositories folder including all the concrete implementations.
In any case, my approach also could be a little bit misleading. So at the end it's only a matter of naming and being used to one or another way. The essence (the important concept behind it) is the same :)
Best regards!