23

TDD is about designing code, guided by tests.
Thus, typical layers aren't usually built upfront; they should slightly appear through refactoring steps.

Domain-driven design involves a lot of technical patterns, defining well established layers like Application layer, Infrastructure layer, Domain Layer, Persistence layer.

To start a DDD project's coding part from scratch, how to behave?
Should I strictly let design emerge from tests, meaning no separation of concerns (no layers) and refactor in order to fit DDD technical patterns?

Or should I create those empty layers (application, entities/domain services, infrastructure) and let TDD fit in each of them independently (using mocks to isolate between layers)?

Mik378
  • 3,838
  • 7
  • 33
  • 60
  • 3
    Related: [Does TDD lead to the good design?](http://programmers.stackexchange.com/questions/178856/does-tdd-lead-to-the-good-design) – Doc Brown May 29 '16 at 19:43

5 Answers5

16

Test Driven Development (TDD) is not a design. It's a requirement that impacts your design. Just as if you were required to be thread safe, that's not a design. Again, it's a requirement that impacts your design.

If you gleefully ignore all other design concerns and religiously keep to the TDD rules don't blame TDD when your code turns into crap. It will be testable crap but it will be crap.

One nice thing about testable crap is that it's refactorable crap so for some people that's good enough. We'll get fancy only when needed. Others hate this and blame TDD for it. No. This is your doing.

Domain Driven Design (DDD) is something you do before TDD's red green refactor cycle.

DDD is the effort to create and preserve a space in the code where a domain expert, who is largely oblivious to the details of the system, can understand how to control the system. This is done by abstraction and modeling a problem domain in a familiar way.

A DDD system can have an architecture that looks like this:

enter image description here

This architecture goes by a lot of names: Clean, Onion, Hexagonal, etc

Here's the disconnect I see many people have when they look at this design. This isn't concrete. I can follow this design and never have written anything you see diagrammed here. I see others insist there must be a use case object or an entities class. What these are is a set of rules that tell you who you can talk to and how.

That's it. Follow the rules of this design and you can TDD your little heart out. TDD doesn't care who you talk to. It cares that everything that does something can be proven to work or not at the click of a button. Not, something somewhere is broken. It tells you exactly what's broken.

Still to vague? Look at the Controler - Use Case Interactor - Presenter diagram in the lower right corner. Here are three concrete things communicating with each other. Sure this is DDD but how do you add TDD here? Just mock the concrete stuff. Presenter must be receiving information. A PresenterMock class would be a good way to check that it's getting what you expected it to get. Hand the Use Case Interactor the PresenterMock and drive the Use Case Interactor as if you were the Controller and you have a nice way to unit test the Use Case Interactor since the mock will tell you if it got what you expected it to get.

Well look at that. TDD satisfied and we didn't have to futz with our DDD design. How did that happen? We started with a well decoupled design.

If you use TDD to drive design (not simply Development) you get a design that reflects the effort you put into it. If that's what you want fine. But that was never what TDD was meant for. What this ends up lacking is certainly not TDD's fault.

TDD is not about design. If you have to make design changes to use TDD you have bigger problems than testing.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • I never said that TDD is a design, but it is **about** design. – Mik378 May 29 '16 at 23:34
  • http://programmers.stackexchange.com/a/167760/43961 – Mik378 May 29 '16 at 23:37
  • Uncle Bob said: "The act of writing a unit test is more an act of design than verification". – Mik378 May 29 '16 at 23:40
  • I count 2 places where you've said some things that can be very easily taken to mean just that. – candied_orange May 29 '16 at 23:41
  • Which ones are they? – Mik378 May 29 '16 at 23:41
  • 1
    Uncle Bob was telling you to design. He wasn't telling you "hey if you're tests work who cares about the rest". – candied_orange May 29 '16 at 23:41
  • I didn't said that TDD is a tool to have a good design. It's a methodology that favors good design during refactoring step. – Mik378 May 29 '16 at 23:42
  • Your opening line and the comment: "I disagree, TDD is not about testing, it's about designing. – Mik378" – candied_orange May 29 '16 at 23:43
  • Well on that we can agree. – candied_orange May 29 '16 at 23:43
  • "Your opening line and the comment: "I disagree, TDD is not about testing, it's about designing. " => there's nothing wrong with this sentence. It's well validated by Dan North, Steve Freeman and Uncle Bob. – Mik378 May 29 '16 at 23:44
  • So as you well mentioned the clean architecture, the whole OP's question is: should I let the clean architecture emerge through TDD refactoring steps, or should I start establishing the architecture, with onion boundaries defined and then starting TDD's cycles. – Mik378 May 29 '16 at 23:46
  • 1
    As I already said, just follow the rules of who you're allowed to talk to. The clean architecture isn't something to have a BDUF debate over. Just identify what part you're in and think about who and how you should communicate. It's more agile than you might think. After that ask if it's testable by TDD. If it's not you did something wrong. If it is I hope you were paying attention because good design is more than just testable. – candied_orange May 29 '16 at 23:52
  • 8
    Ugh... I really can't stand the "Test Driven Design" misnomer. You still have to design a bit before you start blissfully pounding away at your keyboard, whether or not you write tests. – RubberDuck May 30 '16 at 00:16
  • @RubberDuck The beauty of TDD is that you don't need any code to start. You don't need ( and shouldn't have) any established design, even minimal, to start TDD cycles. That's the whole point of TDD. – Mik378 May 30 '16 at 09:14
  • 1
    To quote Uncle Bob on this exact point, ["You have to DESIGN period"](http://blog.cleancoder.com/uncle-bob/2016/03/19/GivingUpOnTDD.html). Click there and if you're too impatient to read the whole thing search for those words. You'll find Mr Martin is quite adamant that TDD is no magic bullet and that you must design not only your code but your tests as well if you don't want to live in a very brittle code base. – candied_orange May 30 '16 at 10:04
  • Do you even know what Domain-Driven Design is? You make it sounds like if it was an architecture... – plalx May 30 '16 at 14:58
  • @plax now that is a fair point. I was glossing over DDD awfully fast. How do you like it now? – candied_orange May 30 '16 at 18:03
16

Make sure you review Uncle Bob's recent comments about the role of design in TDD.

Domain-driven design involves a lot of technical patterns, defining well established layers like Application layer, Infrastructure layer, Domain Layer, Persistence layer.

Udi Dahan: "God, how I hate layering." He spends some time discussing it in his talk CQRS - but Different (layering starts at 18m30s)

I would spell your sentence slightly differently; "DDD recognizes that there are a number of concerns common to most business applications and that the solutions to those concerns have different lifetimes".

For example domain concerns, as a rule, need to be flexible -- especially when you are customizing a solution for a particular business. After all, the domain concerns how the company does business, which is to say, how the company makes money and being able to deliver business improvements quickly is free revenue.

On the other hand, you probably don't need to change the persistence component often. The database solution that worked last release will probably also work this release.

The application concerns are somewhere in the middle; they tend to be stable so that the users don't need to learn a new app with every release.

Also, there can be multiple implementations to solve any given concern. For instance, the application may need only a snapshot of its current state -- simply saving a file to disk will suffice. And in your first few iterations, that may be all the domain needs too. But eventually comes a story that calls for ad-hoc query support, and you recognize that configuring a relational database will be a lot easier than implementing one from scratch. And then there's this one feature that would work better in a graph database.

Meanwhile, the CTO wants a version of the app that runs on his phone; the CEO just heard from a guy that publishing an API is the big thing.

Also, the sales team uses a different model, so give us the same app, with a different model. Oh, but we're travelling a lot, so our version needs to work when we are offline and sync up later...

In other words, you apply the tactical patterns from not by implementing empty placeholders and assuming they will get filled in later, but instead by recognizing when you are crossing the streams "Hey, that's persistence code in my domain model, I must not be done refactoring yet."

Laiv
  • 14,283
  • 1
  • 31
  • 69
VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
7

TDD insures your code has all necessary test cases written in parallel to development. This shouldn't effect high level design. Think of it more in the trenches work.

DDD is all about high level designs, language between domain experts & engineers, context mapping, etc. This should be the driver of the application high level design.

These are both shallow explanations of two powerful programming methodologies. But at the end of the day they really accomplish two very different things.

Start with DDD language & context mapping then eventually when you go to write the code begin the practice of TDD. But the practice of TDD shouldn't affect high level design, but it should insure things can be tested. There's a little bit of a caveat here.

I think it might be important to note: You should only practice DDD if the application is complex enough.

Matt Oaxaca
  • 401
  • 1
  • 3
  • 10
  • 3
    I disagree, TDD is not about testing, it's about designing. – Mik378 May 29 '16 at 18:25
  • I am basing everything off the 3 rules of TDD as described by Uncle Bob. – Matt Oaxaca May 29 '16 at 18:30
  • Steve Freeman, author of GOOS book stated : you shouldn t specify any layers or infrastructure before starting TDD cycles. – Mik378 May 29 '16 at 18:31
  • I am unfamiliar with that book, but I'd have to disagree. I don't want TDD to shaping my DI & class graph. – Matt Oaxaca May 29 '16 at 18:41
  • So you practice Test-first, not TDD – Mik378 May 29 '16 at 18:42
  • I could be dated on my TDD, but I follow this: http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd – Matt Oaxaca May 29 '16 at 18:45
  • Uncle Bob never stated that TDD is not about letting emerge design through tests. – Mik378 May 29 '16 at 18:46
  • He didn't, what he describes are the mechanics of writing code. In my answer I did say there's a caveat in TDD where things must be testable so those can and should shape design. – Matt Oaxaca May 29 '16 at 18:49
  • I do see your point what I am describing might not be "strict" TDD, I know some shops that practice TDD and let the design emerge from it. While also pulling in aspects of DDD such as the Ubiquitous Language. – Matt Oaxaca May 29 '16 at 19:04
  • 3
    @Mik378: TDD is not about testing, but is also not primarily about designing - the only design induced by TDD is the design for unit-testability. Every other part of the design must come from somewhere else. I see TDD more as an implementation technique. – Doc Brown May 29 '16 at 19:37
  • I would simply say: TDD is a methodology that **force to think** about good design through each refactoring step; leading to a clean and by essence testable code. Coder should let the design emerge as the tests get specific. Obviously, coder must have a great knowledge of coding principles: SOLID, Design Pattern, AntiPatterns, etc. – Mik378 May 29 '16 at 19:56
6

DDD is about software design.
TDD is about code design.

In DDD, the "model" represents de abstraction of domain, all the knowledge from domain expert.

We could use TDD for code initial software design model. The Domain has business rules and domain models that the test written (firsts) should be green.

In effect, we can code the tests, after designing a domain-driven model.
This book "Growing Object-Oriented Software, Guided by Tests" link-for-buy
Take this approach, with a walking skeleton, hexagonal architecture and TDD.

Source from: DDD quickly - InfoQ

4

Should I strictly let design emerge from tests

No. (Domain Driven) Design by definition should emerge from domain requirements. This is bad idea to let anything else to drive your design, whether it is tests suite, database schema or ... (to be continued)

Or should I create those empty layers (application, entities/domain services, infrastructure) and let TDD fit in each of them independently

(continued) ... or some canonical layers of classes/class hierarchies in your favorite OO language of choice, even if it is a very mature and popular one (after all "millions of flies can't be wrong", right?).

When it comes to DDD, OOP falls short expressing requirements in human-readable form i.e. something that would be more or less clear to a non-programmer. Strictly typed FP languages do a better job. I recommend to read a book about DDD using functional programming "Domain Modeling Made Functional" by Scott Wlaschin

https://pragprog.com/book/swdddf/domain-modeling-made-functional

You don't have to use FP language to borrow some of the ideas from there (not all of them unfortunately), but if you actually read it then you probably will want to use a functional language.

It will also answer your question about how TDD fits in DDD picture. In a nutshell, when requirements are coded in functional style it eliminates the need for vast amount of unit tests as it makes most of invalid states and scenarios unrepresentable / impossible to compile. Of course, there is still place for automated testing in FP project but by no means tests will drive any major design decisions.

To make a full circle let's return to the title question i.e. "How to combine strict TDD and DDD?". The answer is straightforward: there is nothing to combine / no conflict of interest. Design according to requirements, develop according to design (by writing tests first if you really want to do TDD)

KolA
  • 605
  • 4
  • 10