Behavior Driven Design

2022 11 01 head

Domain-Driven Design DDD has been around since Eric Evans published his book about the subject in 2003 [1].

Vernon Vaugh published powerful approaches how to implement DDD concepts into a software solution [2, 3].

How do you validate and test your DDD digital solutions?

Behavior Driven Development BDD is an inversion of how systems are often developed. In some ways, it is an extension of Test Driven Development TDD approach to the whole application.

Acceptance criteria are coded as automated acceptance tests. The software is written to fulfill these acceptance tests.

This approach integrates well with agile frameworks. [1]. The product owner can accept the story based on the test description enforcing his acceptance criteria.

The documentation describing the acceptance tests is automatically generated. These documented acceptance tests are the requirements of the application. This approach is often called specification by example [4].

I strongly advocate that the developers write the acceptance tests and generate the legible description of the tests.

Constraints

2022 11 01 ddd clean architecture

The BDD approach works well when Domain Driven Design is applied as described in the seminal reference texts.

The acceptance tests are often specified against a Bounded Context.

It is cumbersome and difficult to test Anemic Domain Model. Rich Domains are models that have full control of their data and do not rely on external objects to manipulate them. Anemic Domains are models that rely on other classes to validate their data.

An elegant design approach such as CQRS hugely simplifies the definition and implementation of acceptance tests.

I want to keep the understanding of the strategies simple. The Domain Events strategy is nothing more than an event fired from a Context X microservice with information that may interest other contexts.

This strategy is used when we would like to have asynchronous behavior between our contexts, since the context responsible for publishing the message will not expect a return. For instance, the action that the consumer will take based on this event does not matter to him. For it is always important to think about notifying past actions.

Evolution

A tension exists who is in charge of writing the acceptance tests. Experts initially advocated that the product owner or the business analysts should write the tests to validate the work of the development team. Solutions such as Gherkin and Fitnesse were developed to support this approach.

Experience showed the difficulty of maintaining and refactoring test sets written by non-developers. I strongly advocate product owners shall formulate the acceptance criteria. Developers shall always write the acceptance tests and guarantee a reasonable quality of the created code. Use frameworks based on a programming language instead of structured text approaches. Modern development platforms provide powerful refactoring tools for source code but almost none for a regular text.

Refactoring and clean code principles shall permeate a modern software development approach. Avoid archaic solutions requiring expensive manual activities.

Experiments

We are using a small framework to write acceptance criteria automated tests and create the associated documentation.

The library is available as a Java net.tangly:bdd:<version> Java distribution. It is published on maven central.

A documentation is available under BDD Documentation.

A test uses the given, when, then structure and has the following form:

@Scenario("Sell some black sweaters in stock to a customer")
void sellBlackSweaters(@NotNull Scene scene) {
    final int NrBlueSweaters = 4;
    final int NrBlackSweaters = 5;
    final int NrSoldBlackSweaters = 3;
    scene
        .given("The store is stocked with sweaters",
                                s -> create(s, NrBlackSweaters, NrBlueSweaters))
            .and("has 5 black sweaters in stock", s -> assertThat(store(s).blacks())
                .as("Store should carry 5 black sweaters").isEqualTo(NrBlackSweaters)).
            .and("4 blue sweaters in stock", s -> assertThat(store(s).blues())
                .as("Store should carry 4 blue sweaters").isEqualTo(NrBlueSweaters)).
        .when("The customer buys 3 black sweaters", s -> store(s).sellBlack(NrSoldBlackSweaters))
        .then("The store should have 2 black sweaters in stock",
                                s -> assertThat(store(s).blacks())
                .as("Store should carry 1 black sweaters")
                                .isEqualTo(NrBlackSweaters - NrSoldBlackSweaters))
            .and("4 blue sweaters in stock", s -> assertThat(store(s).blues())
                .as("Store should carry 4 blue sweaters").isEqualTo(NrBlueSweaters))
        .run();
}

Tests are executed as regular JUnit tests. You can easily integrate them in your CI/CD pipeline as integration tests.

The generated documentation is [2]:

As a store owner, I want to update the stock when I am selling sweaters to customers.

tags: 'Release 1.0'

Scenario: Sell some blue sweaters in stock to a customer

given The store is stocked with sweaters and has 5 black sweaters in stock and 4 blue sweaters in stock
when The customer buys 3 blue sweaters
then The store should have 5 black sweaters in stock and 1 blue sweater in stock.

A report example is available under Report Example.

We are experimenting with projects to find out if this approach nurtures legibility and conversation between developers and users. The results are mixed. The integration tests are not easier to write with the library instead of regular JUnit 5 tests. The generated documentation and available JSON reports are valuable. The question is how valuable are they?

We still try to define metrics to valuate the usefulness of living documentation describing the requirements of the product. The usual approach is either to write a huge Microsoft Word document or use a specification tool storing the information in a database. The advantages of living documentation still need to be quantified.

References

[1] E. Evans, Domain-driven design. Addison-Wesley, 2004 [Online]. Available: https://www.amazon.com/dp/0321125215

[2] V. Vernon, Implementing Domain driven Design. Addison-Wesley Professional, 2012 [Online]. Available: https://www.amazon.com/dp/B00BCLEBN8

[3] V. Vernon, Domain-Driven Design Distilled. Addison-Wesley Professional, 2016 [Online]. Available: https://www.amazon.com/dp/B01JJSGE5S/

[4] G. Adzic, Bridging the Communication Gap. Neuri Limited, 2009 [Online]. Available: https://www.amazon.com/dp/B008YZ993W/


1. Scrum advocates recognize the compatibility with their framework. Each story in the product backlog shall have acceptance criteria defined under the product owner’s responsibility. The key is to formulate the acceptance criteria so that they can be coded as automated tests.
2. A JSON export is also available to tailor the generated documentation to your project needs.