It can be too broad a topic to discuss application services
without a specific context. So to that end I am clarifying that the context is
an n-tier
design utilizing object-oriented practices for supporting the modeling of a
common
business application.
What are application services and how
do they fit into an Object-Oriented N-Tier solution?
A middle tier in many business applications is the
business tier. This tier may consist of multiple layers of code including but
not limited to a business objects layer, data translation objects layer, and in
many cases a service objects layer. This
layer of service objects is often called Application Services because it can be
thought of as the essence of the application. I like to think of the service
layer's interface as the application's interface. The business objects
might be the brains of the operation, but the glue that holds the application
together and clearly defines what the application can do is the application
services layer. The presentation layer code can access the application
services and a limited set of business objects and their interfaces in order to
get work done.
An application service’s public API abstracts a
business process with known inputs, outputs and steps as defined by a business
analyst or user. In fact, the code in a well-defined business service should be
readable by a business analyst. The result of creating a service class in front
of all the object manipulation, decisions, and system state changes going on
during a process is a higher level of abstraction. This pattern is essentially
a façade
pattern as documented in the quintessential book Design Patterns.
- Separation of concerns (yes, my number one answer for everything)
- Application Services can provide a façade to complex object models or common tasks involving multiple objects.
- When requirements unrelated to the core business like security or logging is needed we can implement this code outside of the business objects yet guarantee the code is always invoked.
- Encapsulate code that changes the state of the system and may require transactions while allowing business object methods to be side-effect-free and easily testable.
- Help model a process as defined by business analysts
- Complex logic that is often viewed as a flowchart or procedural-logic-tree by business analysts can be written in a service. A business analysis could even step through the code and understand it if written well (using the same terms and phraseology of the business analysts). The brains of the operation stays in the business objects, but the service can manage the process.
- Help our object model evolve in an iterative fashion
- Object models evolve over time as requirements get more stable, changes become necessary, development paradigms shift, and we learn more about the application we are building. Sometimes, just getting the code written in a black box manner (W and X go in, Y and Z come out) can help us see our needed object model as we refactor the code.
My rule of thumb is that if the call changes the
state of the system then it should be in the service. There are many benefits
to side-effect-free
functions and knowing that every business object’s method
is side-effect-free it very useful (see also: CQS:
command-query-segregation principle). That said, a
service that does not change the system, but rather calculates a result or
determines a collection of result objects might not have a clear place to live.
In this case, my first vote would be to put the method in a corresponding
object, an object that appears at the top of the hierarchy. OO
heuristics would state that the method goes with the object
that contains the most data involved in the logic. However, if this object is
not very obvious then this is just another benefit of services. Instead of the
client looking through the interfaces to all the related objects, hopefully
they take a quick peek at the related application services.
·
State is never really needed unless
the service provides sequential calls that rely on data produced in prior
calls. Even then, the client could hold that data and spare the service this
concern.
However, it can be
very helpful for client code if the service class maintained state from call to
call. This kind of a service might be called a Manager Service since besides
just providing simple services, it helps manage the operation. For example a
Quiz Manager might have methods for stepping back and forth through Questions
and allowing the client to submit and change answers. The manager service would
keep track of what question the client is on and whether or not the test is
complete etc.
·
Without state the service can be used
without modification for both stateless and state-full clients. For example a
web front could use the service in the same way a Windows desktop application
would.
·
Without state being shared between
multiple calls in a service, each individual call can be tested separately from
the others.
This is a valid
point, however hopefully the testing of the service is just a test of the
process flow. The actual brains of the operation that will get the most testing
will be the methods on the business objects that the service talks to.
·
If you give a service state it can
mean that less objects need to be exposed to the client. For example, if step 1 on a stateless service
creates object X then you have to return object X to the client, even if the only
purpose for X is to pass it in to service method step 2. If the service had
state, object X might never be exposed and the step 2 would just check for
existence before doing its thing.
·
A class without state could also
provide its methods at static (shared in VB.NET) so a call to create an
instance is unnecessary
The service layer should be in the same tier as
the business object layer so we can take better advantage of object and object
property scope. With a well-designed service layer façade
in the same tier as the business objects many business objects do not have to
be public and ones that are public can keep many of their attributes and
methods internal at worst.
With all services in the same tier as the business
objects, it is easy to see what processes are affected by inevitable changes to
a business object’s interface by a simple compilation of one project (which
again is just a benefit of minimal scope)
From the dawn of object-oriented development there
has been an emphasis on using coding constructs to represent real things in
order to provide a clear mapping from business terms and their counterparts in
code. Possibly even more important than matching up business object names
with real business entities is matching up business processes and scenarios
with services that match the lingo. When a business analyst explains the steps
to determine the result of an algorithm or designs a flow chart with decision
trees, it pays off greatly to match those exact steps, flows, and decisions in
code. If the objects are named after real life entities, and the methods named
after real life questions then you will find that the code in a service reads
just like the documentation of the process, and it should. On many occasions I
have had a business expert looking over my shoulder reading the code that
steps through a process with no confusion on their part in regards to
programming syntax.
We should keep in mind though that the business
rules to be applied and decisions to be made are all done by business objects.
The application service just knows the steps, the details are in the business
objects.
Nice: If myPerson.CanVote then
Not so nice: If myPerson.Age >= 18 then
What I mean by this is that we need a place to put
all the code that has to do with the fact that our business is now represented
in software, but has little or nothing to do with the actual business. For
example, if I want to transfer money between two accounts in a banking
application I would probably use two Account objects and a Dollar Amount
object. However, because this is an application, when someone requests to
transfer money I might have to verify security and write to an event log that
tracks things people do with the system, etc. I want to make sure that this
always happens when a transfer is completed. So instead of making sure the
phone system, web site, and Windows application all complete these same steps I
make sure that the only way to complete a transfer is to use the service I
provide. The business objects know nothing about logging and security, it’s not
their concern, but the service makes sure that these things are taken care of.
Factories
are a service, granted, a specialized service that is more cohesive in regards
to the objects is serves up. Factories
create objects. They might be used for a simple separation
of concerns so a class is not concerned with how
its objects are built, or more complex reasons like data translation and abstract
object creation. However it can be unclear where
the logic exists for determining what object or objects a client should get on
request. For example, if client code needs to get Documents related to an
Account Owner’s Brokerage Account, it would be natural for the client to ask a
DocumentFactory. However, what if determining the Documents an Account Owner
can see is rather complex and requires information not coupled to documents
like security concerns, or we want to log every time someone requests
documents? In an effort to keep the Document Factory cohesive and uncoupled to
security and logging concerns we might consider a service. The possible
downside is that client code does not know if it should look to the Documents
Service or Factory to get the object. However, if the factory does not have a
public method for getting documents, a service would be the next logical place
to look.
I
have read, heard and engaged in discussions about how application services
amount to a procedural design: the antithesis of object-oriented design. I will
admit to some truthfulness in that statement, but would suggest it is too
broad. Rather, services that act as god classes
are not object-oriented, but well-designed classes that perform a service
constitute a solid design. In the seminal book Object-Oriented
Design Heuristics, the object HeatFlowRegulator
is an example of a Service class done right.