Bad code practices : don’t review them, but write code to ban them.

This is getting boring when you are in charge of software quality :

“- stop sending primitive values in your parameters”
“- remove this foreign data structure from the code, it is error prone”
“- why can’t you just isolate this in a separate role ?”

I found myself repeating over and over these kind of sentences. But in 2019, there are new tools that help me become more persuasive. Instead of just using the well known Programming Mistake Detector tools like sonar or checkstyle…
…Some of them now allow to write rules as code, and to make them mandatory at build time.

So now, the code is responsible not only for doing his job, but also to make sure that the developer does not mess with it.

So I have (re)-defined some roles in domain driven design projects, here they are below :

package com.company.program.domain.transverse

import org.springframework.stereotype.Component
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service

object DomainDrivenDesignAnnotations {
/**
* The following role is the intention revealing code of the API.
* It must be written in declarative design and cannot be a facade to the code.
* The abstraction level must be respected in order to let the action
* describe the end to end.
*
* The action can only have a constructor and a public method, 0 private method
*/
@Component
annotation class Action

/**
* The following role is an orchestrator of several Repositories, Gateways
* and computation. It can act as a facility to tackle the complexity
* of the business logic that the action class cannot describe without
* running the risk of becoming too difficult to read.
*
* The domain service can have several public methods, however we strongly
* recommend that all those methods are semantically relating to the same
* use case
*/
@Service
annotation class DomainService

/**
* The following role is a caller to an external / foreign service
* You will find a SOAP/REST api call (and only one)
*
* Only one API call per Gateway is allowed, unless if the foreign API
* has different methods to get the same value
*
* A gateway must take in parameters Aggregates, Entities Or Value Types,
* and must return Aggregates, Entities or Value Types
*/
@Repository
annotation class Gateway

// Repository is already declared in spring, no need to redeclare

/**
* The following data structure belongs to a model that is not part of
* the program domain model, because its structure is not compatible.
*
* To avoid that case, we can try to reuse the value types, entities and
* aggregates from the domain model in the repositories or gateways.
*
* Which is not possible all the time because of the interface contracts.
*
* This means that the data structure must be hold inside the infra package,
* and should not be read or written by the actions and domain services.
*
* The foreign model must come with a converter
*
* ____________________________________________________________________
* | you are not allowed to pull any of these roles in the domain |
* | package, neither in an Action or a DomainService |
* -------------------------------------------------------------------
*/
annotation class ForeignModel

/**
* The following data structure is the root information to pass in an action
* or to be returned from an action.
*
* Only composite data structures can be Aggregate, and an aggregate
* cannot be a value type unless if the action has no access to
* Gateways and Repositories
*/
annotation class Aggregate

/**
* Indicates that the following data structure has no reference
* and cannot identify any repository value
*/
annotation class ValueType

/**
* Indicates that the following data structure contains at least one reference
* in a storage either in this program or in a foreign service
* Therefore must be considered as globally unique and identifiable in the system
*/
annotation class Entity
}

I took some time to describe those roles to my team (+ contributors) and to let them know how it impacts positively the transparency and the proficiency of the code.

The majority of them acquired the paradigm, and some others needed (and still need) to be guided.

So I have written some linter rules, with ktlint, that everyone can check out to apply them in any project : https://github.com/libetl/domaindrivendesign-ktlint-rules

You can add support for ktlint into your project (and also detekt to catch even more code smells), and then you can add those user-defined rules to enforce the use of domain driven design patterns.
Just add a ktlintRules(project(“:domain-driven-design-ktlint-rules”)) directive in your gradle project classpath.

Here are the user-defined rules contained in this project :

AClassWithoutFunctionMustBeADataClass.kt
ActionOnlyHasOnePublicMethod.kt
AllClassMembersMustBePrivateAndImmutable.kt
AllNonForeignDataClassesMembersMustBeImmutable.kt
DataClassNotAnnotated.kt
EachRoleShouldBeInTheRightPackage.kt
GatewayOrRepositoryMustHaveOnlyOneTemplateVariable.kt
NeedsOneCallToAnActionFromAController.kt
NoBreakOrContinue.kt
NoForOrWhileInActionClass.kt
NoForeignInfraUsageInInfra.kt
NoForeignModelInAnnotatedComponentContract.kt
NoGenericCatch.kt
NoPrimitiveObsessionInAnnotatedComponent.kt
NoTemplateUseInActionOrDomainService.kt

From now on, all these rules are correctly respected in the project and everyone can focus on flow instead of form during code reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.