Many experienced developers are longing for an opportunity to build an application from scratch. Compared to the heavy legacy applications, which are seen as filled with technical debt and incomprehensible architecture and design choices, a fresh start promises that you can finally write code and build an application "the right way."
Experience has taught us that complexity creeps in the code regardless of the approach we apply. What if the very principles we claim to value so much could actually contribute to making the code harder to read and maintain in the long run?
Few weeks ago I was fortunate enough to present a talk on this topic on stage at NDC Oslo. The presentation was centered around a back-end application for integration between three different systems based on TDD, DDD and hexagonal architecture.
To equip yourself with the best possible mindset on deciding to use any of the principles mentioned here, I present the following short tradeoffs to consider
Usually, I argue strongly in favor of using Domain-Driven Design whenever we deal with complex domains. The essence of DDD remains modeling and aligning the model in the code with the stakeholder's mental model. Sometimes, all the other stuff just add less value than it is worth. Start with the model and figure out how to put the model to a test, preferably by deploying to production as soon as possible. If in doubt, choose the model that let's you test your assumptions in production sooner rather than later.
Years ago, I admit I was rather strict about applying TDD almost everywhere. I was convinced that using TDD everywhere helped me maintain the quality standards I was striving for.
Now, I do recognize that there are portions of the code where investing in TDD might not yield the benefits with the regards to the effort required. This dilemma aligns well with the concept of core domain of DDD. Within the boundaries of the core domain, I strongly recommend practicing TDD, on the border of requiring it! In particular, by formulating tests as scenarios you get to explore the domain and deal with complexity, one scenario at a a time, much better than jumping into the rules straight away.
Outside of the core domain, you can consider relaxing the constraint. What I use as a rule of thumb is the cognitive load of a code reader when reading the code without tests. Put yourself in the position of the code reader and see if you can do without TDD on the edges of the application.
When questioning the choice of an architectural style pay attention to the evolution of the code base. In the case of hexagonal architecture it is interesting to observe what happens with the code after e.g. isolating the domain model. If we follow up with adding more abstractions around the domain model than strictly necessary, ask yourself if the added abstraction layer lead to less or greater complexity?
I suggest to establish the abstractions that help isolate the domain model in the code structure, the abstractions such as namespaces, projects, and repositories that help keep the domain layer as independent as possible. Hold the number of abstractions outside the domain model to the minimum. At least calibrate it with the feedback on what part of the code base is easier to traverse with the abstractions in place and what parts could benefit with flat structure.
It is all about having a healthy dose of skepticism with respect to your principles. Every project we embark on is a chance to evaluate the applicability of the principles you hold dear and by doing so you might as well re-discover the true motivation behind them.
For more on the topic read the original blog post with the Q&A-session in the comments based on the interaction during the presentations at multiple conferences.