| Business logic should not happen at API boundaries (regardless of what kinds of technologies or transport those interface boundaries are facilitating). Interfaces should be a validation and translation layer that receive incoming data, hand off to business logic, receive outgoing data and perform whatever translation and serialization required to satisfy the return/response interface. Business logic should be completely isolated from that responsibility, and ideally transport-agnostic. In a traditional “MVC” style architecture this would be described as “thin controller”. But that kind of architecture often tightly couples other things that shouldn’t be, because they hide other transport/interface boundaries. The way I approach this is pretty simple: 1. Interfaces between user input and “programs” of any kind are used for validation, sanitization, deserialization and interface translation. Only. No state changes can occur here. In the language of the article, this is the “thin backend”. 2. Any runtime business logic, using any technology for that runtime, clearly defines its input and output types. This layer is allowed to make local state change as appropriate, but any external side effects are forbidden. 3. If the layer described in #2 needs to communicate with an outside network, system, storage or service layer, recurse to #1. Contracts between these boundaries are only that and imply no implementation details. 4. If you implement #3 in the service providing #2, those responsibilities are explicitly separated. 5. Do business logic. Return data consistent with the business logic’s contract. 6. Rewrap the onion and eventually serialize the result for the outside client according to that layer’s expectation, at whatever interface boundary you’re at. |