I like programming in such a way that every component/injectable of the applications I build, has a clearly defined scope, and it's easily tested.
With years working as a Developer I've come to ensure every component I declare and implement fits one of the following categories:
Technical components: those behind the scenes over which everything works. Usually not implemented by us, but part of some library. Database connections, server engines, http clients, etc.
Business logic components: they describe and encode business rules, and never deal with stuff like Http request parsing, Db connections, or serialization/deserialization of data. (OrdersService, UsersService, etc)
Boundary components: They deal with the translation between business intents, and technical operations (ie, translating the intention of creating an entity, into an
insert
statement, or parsing the body of an Http Request, into a business intent, or encoding into JSON the result of some business operation). DAOs/repos, controller and API clients are usually among this category.
That being said, no business logic components never interact directly with Technical components, but rather other business logic components, and boundary components.
Now, when encoding business rules, one should use business logic components, right? but what if such rules need to be run under some kind of transaction that is DB dependent?
How do I encode multiple business steps that need to run under the scope of some transaction, without a leaky abstraction as a result?
So let's say I want to add a payment, and then change the state of an order from Pending
to Processing
It's a business rule that when one happens, the other must happen too. Yet those to actions belong to two different domains, and yet, they must happen transactionally... These steps must be encoded in a business logic component, and without leaky abstractions, still express the fact that they belong to some transaction. How???