Tag: architecture

Does TDD “damage” your design?

I recently came across a couple articles that challenged some of my beliefs about best practices.

In this article, Simon Brown makes the case for components tightly coupling a service with its data access implementation and for testing each component as a unit rather than testing the service with mocked-out data access. Brown also cites David Heinemeir Hansson, the creator of Rails, who has written a couple of incendiary articles discouraging isolated tests and even TDD in general. Heinemeir Hansson goes so far as to suggest that TDD results in “code that is warped out of shape solely to accomodate testing objectives.Ouch.

These are thought-provoking articles written by smart, accomplished engineers, but I disagree with them.

For those unfamiliar with the (volatile and sometimes confusing and controversial) terminology, isolated tests are tests which mock out dependencies of the unit under test. This is done both for performance reasons (which Heinemeir Hansson calls into question) and for focus on the unit (if a service calls the database and the test fails, is the problem in the service or the SQL or the database tables or the network connection?). There’s also a question of the difficulty of setting up and maintaining tests with database dependencies. There are tools for that, but there’s a learning curve and some set-up required (which hopefully can be Dockerized to make life easier). And there’s one more very important reason which I’ll get to later…

Both Brown and Heinemeir Hansson argue against adding what they consider unnecessary layers of indirection. If your design is test-driven, the need for unit tests will nudge you to de-couple things that Brown and Heinemeir Hansson think should remain coupled. The real dilemma is where should we put the inevitable complexity in any design? As an extreme example, to avoid all sorts of “unnecessary” code you could just put all your business logic into stored procedures in the database.

“Gang of Four” member Ralph Johnson described a paradox:

There is no theoretical reason that anything is hard to change about software. If you pick any one aspect of software then you can make it easy to change, but we don’t know how to make everything easy to change. Making something easy to change makes the overall system a little more complex, and making everything easy to change makes the entire system very complex. Complexity is what makes software hard to change. That, and duplication.

TDD, especially the “mockist” variety, nudges us to add layers of indirection to separate responsibilities cleanly. Johnson seems to be implying that doing this systematically can add unnecessary complexity to the system, making it harder to change, paradoxically undermining one of TDD’s goals.

I do not think that lots of loose coupling makes things harder to change. It does increase the number of interfaces, but it makes it easier to swap out implementations or to limit behavior changes to a single class.

And what about the complexity of the test code? Brown and Heinemeir Hansson seem to act as if reducing the complexity of the test code does not matter. Or rather, that you don’t need to write tests for code that’s hard to test because you should just expand the scope of the tests to do verification at the level of whole components.

Here’s where I get back to that other important reason why “isolated” tests are necessary: math. J.B. Rainsberger simply destroys the arguments of the kind that Brown and Heinemeir Hansson make and their emphasis on component-level tests. He points out that there’s an explosive multiplicative effect on the number of tests needed when you test classes in combination. For an oversimplified example, if your service class has 10 execution paths and its calls to your storage class have 10 execution paths on average, testing them as a component, you may need to write as may as 100 tests to get full coverage of the component. Testing them as separate units, you only need 20 tests to get the same coverage. Imagine your component has 10 interdependent classes like that… Do you have the developer bandwidth to write all those tests? If you do write them all, how easy is it to change something in your component? How many of those tests will break if you make one simple change?

So I reject the idea that TDD “damages” the design. If you think TDD would damage your design, maybe you just don’t know how bad your design is, because most of your code is not really tested.

As for Heinemeir Hansson’s contention that it’s outdated thinking to isolate tests from database access, he may be right about performance issues (not everyone has expensive development machines with fancy SSD drives, but there should be a way to run a modest number of database tests quickly). If a class’s single responsibility is closely linked to the database, I’m in favor of unit-testing it against a real database, but any other test that hits a real database should be considered an integration test. Brown proposes a re-shaped, “architecturally-aligned” testing pyramid with fewer unit tests and more integrated component tests. Because of the aforementioned combinatorial effect of coupling classes in the component, that approach would seem to require either writing (and frequently running) a lot more tests or releasing components which are not exhaustively tested.

Observations about “Clean” / “Hexagonal” architecture

Introduction:

After seeing Cyril Martraire’s online talk (in French) on Parleys about “Hexagonal” architecture and having read Robert C. Martin’s take on the “Clean Architecture”, I wanted to try it out for myself. It sounded great in theory, but reading a high-level description of an approach doesn’t teach as much as being confronted by numerous low-level implementation choices that result from the choice of high-level approach. I set up a little (but growing) personal project, and got going.

Approach:

Following Cyril Martraire’s simplified approach, packages are either domain or infrastructure. The dependencies go only from infrastructure to domain.

The domain contains client-facing service objects (ex: AccountsService) and interfaces for low-level (but domain-focused) services used by them (ex: IAccountsStorage, ISecurity, IEventListener, IEventPublisherClient). It also contains value objects (ex: UserAccount).

Infrastructure contains everything else (ex: concrete implementations of the low-level domain-focused services, HTTP front ends, command-line controllers). My little project, in Java, relied on PostgreSQL for persistent storage, Redis for short-term session storage, and JBoss Wildfly for websocket and REST endpoints, a JMS queue, etc.

hexArch

 

Useful Patterns:

 – CRUD happens with a call to a service object (ex: createUserAccount() in an AccountsService object), which then relies on the infrastructure implementation of the service’s storage interface (ex: PostgresAccountsStorage which implements IAccountsStorage) and other low-level interfaces (ex: BCryptSecurity implements ISecurity).

To unit-test the service, it’s best to stub and mock the low-level interfaces and verify validation of inputs, error handling and correctness of the call to the storage interface (for example, was the password which was passed to IAccountsStorage.createUserAccount() transformed by ISecurity? Note that you must not test the real encryption here because it’s a domain-level test – use stubbing, such as Mockito.when()).

In the infrastructure, it’s useful to refactor the storage tests to put all tests for the interface in an abstract class (in the domain, not in infrastructure), and then the concrete implementation of the tests just implements setUp() to provide the class under test. For example PostgresAccountsStorageTest just provides a real Postgres connection in setUp() – if I decide later to switch to MongoDB, I just implement a new setUp() with a real MongoDB connection in MongoAccountsStorageTest and I have all the failing tests already written in the superclass, AbstractAccountsStorageTest. Note that the tests in the AbstractXXXStorageTest classes are the only ones which actually access a real database.

HexUML

Addendum: The pattern I describe for CRUD here seems to fit nicely with J.B. Rainsberger’s notion of contract tests and collaboration tests. The service tests are what he calls collaboration tests (where services are mocked) and then the tests which actually touch the database are what he calls contract tests, because they prove that the service really does what is asked. I like this terminology better than “unit” and “integration” tests, because the names reflect their function in verifying the implementation rather than just a notion without nuance of their scope. 

 – I also found it useful to create a session controller class in the infrastructure to act as an intermediary between the server endpoints (websocket, REST, etc.) and the domain service classes (more on this later…).

Difficulties and limitations:

  • Logging – Is it a domain element or an infrastructure element? Risk of a multiplicity of logging solutions because some infrastructure elements will have their own logging dependencies (ex: log4j). I chose to have a domain-level logging interface implemented using slf4j-logback in the infrastructure. This may be over-engineering, since slf4j is already a facade.
  • Extracting a low-level component – Tricky to try and pull out an infrastructure unit developed to make it a general-purpose stand-alone library because the infrastructure depends on the domain (including the domain-level logging interface – difficult to extract).
  • Domain classes can’t contain Java-EE annotations and the like. Auto-wiring CDI dependency injection can get a bit complicated and may require wrapper classes or somewhat complex Producer implementations. Java EE was not designed with a clean/hexagonal architecture in mind.
  • There are some gray areas for design. In particular, the session controller is responsible for calling domain services in a certain way and enforcing a certain flow of control. Flow of control seems like infrastructure to me, whereas the “certain way” of calling domain services (ex: the interpretation of a text command) seems like domain code. When in doubt, it’s infrastructure (the domain stays pure), but the need for some refactoring could emerge from this ambiguity. I will probably end up refactoring out infrastructure dependencies to put the control code in the second of 3 concentric circles (dependent on the domain but not the infrastructure) – which is closer to what Robert C. Martin proposed (though his diagram would put my session controller in the same circle with my PostgreSQL storage implementations, for example). This additional “control” circle seems to correspond to the ports layer described in this InfoQ article.
  • Finally, it’s impossible to eliminate absolutely all infrastructure dependencies from the domain code, because even the choice of language has certain infrastructure implications. If you code in Java, the JVM is part of your infrastructure, and you can’t use Rails, Django, Node.js, Xamarin or Unity, for example. I suppose you could write the domain code in Ruby or Javascript, which are JVM-compatible languages that also work on other types of infrastructure.

Advantages:

  • Easy to swap out or offer alternative infrastructure elements (ex : change from Postgres to Mongo) without modifying the domain code at all and possibly without the need to write new tests.
  • Domain code is easier to write, read, update and test because it’s unpolluted by infrastructure. Coding the infrastructure is often easier, too, because the interface with the domain is clear and clean.
  • Can delay decisions about infrastructure and presentation, making progress on the domain logic.
  • Easier to port domain code to other projects and/or infrastructures.
  • Infrastructure development is driven by the domain use cases (YAGNI is easier to enforce)
  • Facilitates IoC and DI, which makes the code easier to test, which makes the code easier to refactor, which makes the code cleaner, which makes changes easier to implement.

Disadvantages:

  • Can’t lean heavily on a framework (ex: CDI, ORM, MVC, AOP, JMS/MDB), especially in the domain (where no framework dependencies are allowed). Leaning on frameworks can be useful for enforcing canonicality (having things defined in only one place – for example: strictly speaking, none of your domain classes should be generated by or have dependencies on an ORM – not even JPA annotations – so in a strict clean architecture approach you can’t use an ORM to generate your domain classes from database tables or vice-versa).
  • Can’t easily optimize the domain code for specific infrastructures.
  • Initial progress can be slow (as is the case with TDD in general).
  • Domain logic changes can ripple out into multiple infrastructure adapter implementations.
  • Complexity and redundancy in TDD, because use cases often need to be unit-tested and developed in multiple layers (ex: servlet, controller, service, and storage). If the end-to-end testing cycle is slow (heavy app server restart, WAR/EAR redeployment, etc.), forgetting to add a test at one of these levels can cost a lot of time.

Some thoughts about microservices:

The hexagonal/clean architecture is not the same as a microservices architecture, but the two are not completely incompatible. In a hexagonal/clean architecture, the goal is to separate the domain from the infrastructure. In a microservices architecture, the goal is to split the domain into small manageable pieces which communicate and inter-operate via a messaging infrastructure (REST, SOAP, JMS, ESB, whatever). According to Martin Fowler, you can’t start a development project using microservices. The domain has to be sufficiently complex (and useful/lucrative) to justify the effort and heavy tooling involved in managing a microservices approach, and the domain split has to be clearly defined. So a hexagonal/clean architecture might be the right place to start before evolving toward microservices. Because in the hexagonal/clean approach the domain is cleanly separated from infrastructure, splitting up the domain should be easier. Each microservice will start with a clean piece of the domain with interfaces to adapt to whatever infrastructure is needed for the specific microservice. It may even be possible to port much of the existing infrastructure code to the microservice, or to replace it with lighter infrastructure.

Conclusion:

The hexagonal/clean architecture makes it easy to have and maintain quality code in the domain layer. It makes it easier to port the domain code to different infrastructures. The model/CRUD side of infrastructure is clearly and cleanly driven by the needs of the domain. The view/presentation infrastructure relies on well-defined service API’s to interact with the domain. These clear divisions of responsibility remove some sources of accidental complexity.

The approach seems to be a good one for most kinds of projects. However, for very small projects, for throwaway prototypes and short-term projects, and for projects requiring painstaking optimization for a specific platform, it might not be the best choice, but for all others, it seems like a very good choice for developing well-crafted, maintainable software which can evolve as infrastructure needs and possibilities change.