Done Right

2023-01-27

What your development team does usually start designing a new application from? How long does it take to see in action the first few use cases done? Do you start with some framework, or maybe from designing a database schema? Do you require two weeks to design a development stack and deploy a server environment?

The problem of unclean software architecture design affects software engineering companies, their products, and their users. The impact of this mainly is the codebase that resists change and consequent low development velocity. The successful solution obviously would be just to follow some core design principles, like layers design. But who cares when you need the product yesterday, can start from a well-known (but also “hard to hire right people”) tech framework like Rails today, and get the first feature tomorrow?

Business Logic

I will start right from the Value in a new dependencies-free code base. The value is among use cases that satisfy certain needs of the actors, getting some input data, processing it, and responding with the output data.

This part of a system is called “Business Logic” and it is the heart of the system. There is no reason for the app to exist where there is no actor who will interact with it. There is no reason to change the business logic without changing the needs of the actors.

Any system does not exist in a vacuum, and the system certainly will store some part of the data inside and plausible consume other data from the system environment. But these things are extraneous to the domain business logic, and technical decisions and assumptions done at the time will certainly evolve during the system lifecycle. That is why all the logic extraneous to the domain should be isolated into Plugin interfaces that belong to your domain and not to the environment.

All the domain services will be developed based on plugin interfaces tailored to the domain. It will be thoroughly tested by using mocks and stubs of those plugins. My choice there is Minitest, unit- and spec mixed style, depending on the test subject.

It might seem sort of extra fancy work at the beginning, but it is the first and only chance to protect your business logic to be penetrated and contorted by techs with the techs vision of things not your actors.

Today when I developed my Punch code generator I’m expressing domains in a convenient DSL describing actors, services, and entities; then I just generate the whole domain skeleton. So “extra time” there is actually drastically reduced time with extra added quality.

Domain Face

To give actors the ability to interact with the domain it must provide one or more interfaces. So my next step will be developing the domain “face” that serves as a bridge between the domain and the user environment.

“Face” often could be seen as a project scope where the scope implies one or more particular technologies like web/desktop application, or API. Basically any “face” will just adopt the domain to one particular environment by giving some sort of request-response cycle. Assuming an HTTP interface as the example, it will receive HTTP Request, translate it to the domain, calling one of the domain services, translating domain response back to HTTP, and return the response to the user.

When you have a separate domain business logic layer, you can equip it with as many faces as you need, and all you should do there is just provide request-response cycle, domain service location, translation tech request to the domain langue, and set of presenters for gotten domain responses.

So at this stage I will provide all the necessary stuff described above and thoroughly tested request-response cycle and presenters.

At this moment I have accustomed a few basics techs for domain faces so when my app requires dRuby, Rack, or RabbitMQ - I just copy my previous already tested code, maybe except for particular presenters.

Plugins

It’s important to highlight here that up to this moment the development process was free from heavy dependencies like third-party servers, and all plugin interfaces were mocked and stubbed for test purposes. Now is the right time to implement the required plugins.

When one has a plugin interface first, one can develop shared tests based on the interface that will serve all possible plugin implementations. And again, when you have implemented some “tech” plugin once, you probably can use it for other apps, except its interface can vary between the domain purposes. For most apps maybe the first plugin will be the data store. It might be plain files, SQL/NoSQL, Redis, etc., and it tends to stay the same.

The story is the same as with faces when you have accustomed once, the next usage will usually cost you 80% less effort.

Integration

When all plugins are implemented your app is almost done but yet requires some integration testing… At this stage, I usually design some simple client script that plays for the domain actors connecting to the app and requesting its services.

Pitfalls

The most dangerous software thing is dependencies and one should always have it on top of one’s mind. Especially harmful are dependencies from “techs” that could easily enslave and spoil your domain. So ensure that your application components depend on business logic and not backward.

For example when one starts some rich business logic app from an all-in-one tech framework not paying much attention to the app domain, one will likely come to the wrong discussions like “thick controller VZ thick model” and “N+1 Query”

MVP

When you develop some sort of MVP, you should rather go for the simplest possible plugin implementation first that serves the purpose and skimps resources. In the case of the data store for an example, it might be just an in-memory store, maybe even with just a few collections preloaded.

Numbers

The practice of two real commercial applications and a few pet projects speaks that: