Recently I started
a discussion with another software architect about where to implement the
business logic in a SOA-like business layer. There would be service classes
with methods which implement service functions like "bookFlight" or
"upgradeFlight". We also introduced a "model" which
consists of domain objects, holding the data to be manipulated by the services.
The domain objects would also be stored in and read from a database and would
serve as parameters for our service, meaning that they would be transferred via
JSON or XML to the service consumer. Now, the question is: where should we
implement the business logic? Should all of it be in the service functions or
should all logic go into the domain objects or should we have a mix?
But what is
business logic anyway? Validation is often cited here, but that’s not all of
it. More important are algorithms, calculations, rule evaluations and
transformations which are driven by business requirements. Consider a domain
object “passenger” which represents the customer of an airline. The passenger
might have a method “getSignificance()” which returns the value of importance
that the passenger has for the airline. This significance can depend on many
things, for example his frequent flyer status, his age, his flight frequency,
his marital status, the people he know at the airline etc. The algorithm to
calculate this value also can be very complicated and it can change frequently.
The significance of the passenger is so important, that it is calculated very
often and it always refers to one single passenger. Makes it sense to implement
“getSignificance()” at the passenger domain object?
The other
solution would be to implement it in the service, e.g. in the “bookFlight”
service, which uses it to calculate the price. But what if “upgradeFlight” also
needs to know the significance of the passenger? Would you just duplicate the
code and put it in the other service as well? That would lead to messy maintenance
problems and a lot of discussion with the QA people (and for good reasons!).
Many people
argue that domain objects without business logic would lead to a “clear
separation of logic and data”. But, what are we talking about? It is just that,
namely the combination of data and logic in objects, what the object oriented
programming once set out to achieve. Thus, logic-less domain objects would
rather indicate procedural programming, leading to something old-fashioned like
a transaction script which is encoded in the service methods. It really reminds
me of the old C programming style, putting all data in “structs” and just
weaving some procedural code around it. But after all, we want to be
object-oriented, don’t we?
The term
“domain object” comes from “domain model”, meaning that all these classes make
up the model which represents the business and problem domain. In object
oriented modeling you simply identify all the objects which occur in the
business domain and then you create one class for each object type found. Of
course, the business domain not only contains information (“data”) – it also
contains actions and activities (“methods”). Thus, domain objects naturally
should contain logic as well.
Martin
Fowler once coined the term “anemic domain model”
(http://martinfowler.com/bliki/AnemicDomainModel.html)
for domain objects without logic. He calls it an “anti-pattern”. And I think he
is right. But on the other hand, I don’t think that all the logic always should
go into domain objects.
Domain
objects should contain only the business logic, which is closely related to the
data of the object and to the business entity’s meaning which corresponds to
the current domain object. E.g., the basic logic to upgrade a passenger should
not go into the passenger domain object (Although language seems to indicate
that the passenger is upgraded that is not so. The booking is upgraded.). If
there is a “booking” domain object the logic belongs there or into the service
if not. Also, if many other domain objects are involved in the algorithm, this
kind of logic should also go rather into the service class.
I would
expect a service method implementation to look something like this:
myServiceMethod() {
domainObject do1 = dao.fetchNeededDomainObject();
do1.doSomething();
if (do1.getSomething()) == someValue) {
// some logic goes here
domainObject do2 = dao.fetchNeededDomainObject();
do2.doSomethingElse();
do1.alter ();
}
// maybe some more logic goes here as well
return do1;
}
As one can
see, there is business logic in the service method. It is that logic, which is
needed to coordinate the work of the domain objects and to drive the overall
process. The services delegate most of their work to domain objects. Or, to put
it another way, only that logic, which is not clearly bound to one domain
object but rather to the overall process, should be found in the service.
Nevertheless, domain logic must stay separate from persistence and presentation
logic. This can be achieved easily e.g. by using the DAO pattern and by adding
an extra presentation layer on top of the services. .
One further
point: what about using the domain objects to transfer data? They could e.g. be
transformed into JSON or XML and streamed to a client. Would that indicate that
we should get rid of all the logic, since it could not be transferred so
easily? But, as I just described, it is not the domain object that is
transferred, it is a marshaled representation of it – meaning that only the
data of the domain object is transferred. If the client was a Java client, the
data could be un-marshaled into another domain object on the client, which
would feature its method and business logic again.
So, I am
absolutely in favor of solid object oriented modeling, with a well formed
domain model. And this means, that domain objects must be able to hold logic,
since otherwise they are nothing more than primitive data types in disguise.
No comments:
Post a Comment