How acceptable is to keep business logic outside entities (in separate service classes)

business-logicbusiness-rulesdesign-patternsdistributed-systemobject-oriented-design

We were taught that objects are self contained things with data and behaviour and therefore they should have methods that act on their attributes. But there are several situations when this coupling between entities and their behaviour is not observed:

  • Entity frameworks (like .NET with POCO and Enterprise Java with POJO entities) stipulate that CRUD/persistence/search operations should be done by external entity managers (and repositories) and not by the entities themselves, i.e. we have code entityManager.save(entity) and not entity.save();
  • Business rule engines are the most visible examples of separating business logic from the entities – business rules form completely different code (even in different languages) from entities. E.g. JBoss Drools or IBM ILOG or other rule engines. Business rule paradigm can be used for the extrapolation of OO programming – in OO we consider data and methods, but in semantic web we can consider ontologies and logical/business rules that are acting on the ABox or TBox of ontologies – two completely different languages and reasoning systems.
  • Distributed computing stipulates use of serialization and deserialization of objects and communication of those objects across network – we have XML, native binary formats or JSON for this. Usually only data are communicated over network and business logic is kept in one layer and there is not technology for moving business logic across network and platforms, e.g. there is no automatic translation and communication of business logic when Java entities are translated into JSON objects and exposed through REST API to Angular 2 frontend. Business logic usually is kept in one side (e.g. in Java).
  • It is said that OO domain model should reflect/model real world. And sometimes the real world objects do not have business logic inside them. E.g. there are concepts about calculators, e.g. tax calculators, salary calculators etc. Therefore we write calculator.recalcTaxes(invoice) and not invoice.recalcTaxes. The former approach allow us to apply different calculators in different cases = e.g. across legislations. We are not forced to build complex inheritance hierarchy simply beacuse there are different business methods, we simply apply different business services/calculators to the same data.

Considering those arguments pro separating business logic from data/entities – how acceptable is to make this separation as the general rule of design for my project of business software? What are the arguments against separating business logic from data?

Best Answer

If you are literally talking about business logic as in, it's for a business and not domain logic in general, I find most entities have logic imposed on them by outside influencers or because of the context (We give you a discount if ...). That's why business programming can be so frustrating because things can appear to be arbitrary. Entities don't always match the real world, but the world as the business perceives it under different circumstances. There's only one customer called "Acme, Inc." but we have to have two entries because our system only allows one sales person to be assigned to them, and we need them to have two because of yadda-yadda-yadda someone's cousin. Don't worry, you don't have to do this all the time (as if that matters), so I'm sure you can program the computer to make this one exception when calculating their total purchases.

In the U.S., if you hire a contractor, you're suppose to give them a 1099 tax form if they bill you for over $400. This rule isn't part of the contractor or even a particular invoice, but an external government tax rule that looks at the aggregate of bills for a given tax year. This rule could even fluctuate from one year to the next. Dealing with non-profits may present another concern. You really need some sort of tax.calculator for this.

When you don't separate your logic properly, users may start to use your entities in ways you hadn't planned, because the business logic is too closely tied to the entity. Client address data entry should be so straight-forward, but have you ever seen: AddressLine3 = "c/o John Smith" because the app doesn't differentiate business deliveries from private residences? Since you don't have separate logic for printing mailing labels, users do all sorts of things to work-around the lack of separate address info from address printout logic.

Business rules tend to be a combination of factors and are rarely cut-and-dried for a given entity. You can put logic in a business entity, but I think you'll find yourself refactoring this away when everyone finally identifies how things are really done or will be done.