Home ยป How accurate is “Business logic should be in a service, not in a model”?

How accurate is “Business logic should be in a service, not in a model”?


In order to define what a service’s responsibilities are, you first need to define what a service is.

Service is not a canonical or generic software term. In fact, the suffix Service on a class name is a lot like the much-maligned Manager: It tells you almost nothing about what the object actually does.

In reality, what a service ought to do is highly architecture-specific:

  1. In a traditional layered architecture, service is literally synonymous with business logic layer. It’s the layer between UI and Data. Therefore, all business rules go into services. The data layer should only understand basic CRUD operations, and the UI layer should deal only with the mapping of presentation DTOs to and from the business objects.

  2. In an RPC-style distributed architecture (SOAP, UDDI, BPEL, etc.), the service is the logical version of a physical endpoint. It is essentially a collection of operations that the maintainer wishes to provide as a public API. Various best practices guides explain that a service operation should in fact be a business-level operation and not CRUD, and I tend to agree.

    However, because routing everything through an actual remote service can seriously hurt performance, it’s normally best not to have these services actually implement the business logic themselves; instead, they should wrap an “internal” set of business objects. A single service might involve one or several business objects.

  3. In an MVP/MVC/MVVM/MV* architecture, services don’t exist at all. Or if they do, the term is used to refer to any generic object that can be injected into a controller or view model. The business logic is in your model. If you want to create “service objects” to orchestrate complicated operations, that’s seen as an implementation detail. A lot of people, sadly, implement MVC like this, but it’s considered an anti-pattern (Anemic Domain Model) because the model itself does nothing, it’s just a bunch of properties for the UI.

    Some people mistakenly think that taking a 100-line controller method and shoving it all into a service somehow makes for a better architecture. It really doesn’t; all it does is add another, probably unnecessary layer of indirection. Practically speaking, the controller is still doing the work, it’s just doing so through a poorly named “helper” object. I highly recommend Jimmy Bogard’s Wicked Domain Models presentation for a clear example of how to turn an anemic domain model into a useful one. It involves careful examination of the models you’re exposing and which operations are actually valid in a business context.

    For example, if your database contains Orders, and you have a column for Total Amount, your application probably shouldn’t be allowed to actually change that field to an arbitrary value, because (a) it’s history and (b) it’s supposed to be determined by what’s in the order as well as perhaps some other time-sensitive data/rules. Creating a service to manage Orders does not necessarily solve this problem, because user code can still grab the actual Order object and change the amount on it. Instead, the order itself should be responsible for ensuring that it can only be altered in safe and consistent ways.

  4. In DDD, services are meant specifically for the situation when you have an operation that doesn’t properly belong to any aggregate root. You have to be careful here, because often the need for a service can imply that you didn’t use the correct roots. But assuming you did, a service is used to coordinate operations across multiple roots, or sometimes to handle concerns that don’t involve the domain model at all (such as, perhaps, writing information to a BI/OLAP database).

    One notable aspect of the DDD service is that it is allowed to use transaction scripts. When working on large applications, you’re very likely to eventually run into instances where it’s just way easier to accomplish something with a T-SQL or PL/SQL procedure than it is to fuss with the domain model. This is OK, and it belongs in a Service.

    This is a radical departure from the layered-architecture definition of services. A service layer encapsulates domain objects; a DDD service encapsulates whatever isn’t in the domain objects and doesn’t make sense to be.

  5. In a Service-Oriented Architecture, a service is considered to be the technical authority for a business capability. That means that it is the exclusive owner of a certain subset of the business data and nothing else is allowed to touch that data – not even to just read it.

    By necessity, services are actually an end-to-end proposition in an SOA. Meaning, a service isn’t so much a specific component as an entire stack, and your entire application (or your entire business) is a set of these services running side-by-side with no intersection except at the messaging and UI layers. Each service has its own data, its own business rules, and its own UI. They don’t need to orchestrate with each other because they are supposed to be business-aligned – and, like the business itself, each service has its own set of responsibilities and operates more or less independently of the others.

    So, by the SOA definition, every piece of business logic anywhere is contained within the service, but then again, so is the entire system. Services in an SOA can have components, and they can have endpoints, but it’s fairly dangerous to call any piece of code a service because it conflicts with what the original “S” is supposed to mean.

    Since SOA is generally pretty keen on messaging, the operations that you might have packaged in a service before are generally encapsulated in handlers, but the multiplicity is different. Each handler handles one message type, one operation. It’s a strict interpretation of the Single Responsibility Principle, but makes for great maintainability because every possible operation is in its own class. So you don’t really need centralized business logic, because commands represents business operations rather than technical ones.

Ultimately, in any architecture you choose, there is going to be some component or layer that has most of the business logic. After all, if business logic is scattered all over the place then you just have spaghetti code. But whether or not you call that component a service, and how it’s designed in terms of things like number or size of operations, depends on your architectural goals.

There’s no right or wrong answer, only what applies to your situation.

As for your title, I don’t think the question makes sense. The MVC Model consists of data and business logic. To say logic should be in the Service and not the Model is like saying, “The passenger should sit in the seat, not in the car”.

Then again, the term “Model” is an overloaded term. Perhaps you didn’t mean MVC Model but you meant model in the Data Transfer Object (DTO) sense. AKA an Entity. This is what Martin Fowler is talking about.

The way I see it, Martin Fowler is speaking of things in an ideal world. In the real world of Hibernate and JPA (in Java land) the DTOs are a super leaky abstraction. I’d love to put my business logic in my entity. It’d make things way cleaner. The problem is these entities can exist in a managed/cached state that is very difficult to understand and constantly prevents your efforts. To summarize my opinion: Martin Fowler is recommending the right way, but the ORMs prevent you from doing it.

I think Bob Martin has a more realistic suggestion and he gives it in this video that’s not free. He talks about keeping your DTOs free of logic. They simply hold the data and transfer it to another layer that’s much more object oriented and doesn’t use the DTOs directly. This avoids the leaky abstraction from biting you. The layer with the DTOs and the DTOs themselves are not OO. But once you get out of that layer, you get to be as OO as Martin Fowler advocates.

The benefit to this separation is it abstracts away the persistence layer. You could switch from JPA to JDBC (or vice versa) and none of the business logic would have to change. It just depends on the DTOs, it doesn’t care how those DTOs get populated.

To slightly change topics, you need to consider the fact that SQL databases aren’t object oriented. But ORMs usually have an entity – which is an object – per table. So right from the start you’ve already lost a battle. In my experience, you can never represent the Entity the exact way you want in an object oriented way.

As for “a service”, Bob Martin would be against having a class named FooBarService. That’s not object oriented. What does a service do? Anything related to FooBars. It may as well be labeled FooBarUtils. I think he’d advocate a service layer (a better name would be the business logic layer) but every class in that layer would have a meaningful name.

I’m working on the greenfield project right now and we had to make few architectural decisions just yesterday. Funnily enough I had to revisit few chapters of ‘Patterns of Enterprise Application Architecture’.

This is what we came up with:

  • Data layer. Queries and updates database. The layer is exposed through injectable repositories.
  • Domain layer. This is where the business logic lives. This layer makes a use of injectable repositories and is responsible for the majority of business logic. This is the core of the application that we will test thoroughly.
  • Service layer. This layer talks to the domain layer and serves the client requests. In our case the service layer is quite simple – it relays requests to the domain layer, handles security and few other cross cutting concerns. This is not much different to a controller in MVC application – controllers are small and simple.
  • Client layer. Talks to the service layer through SOAP.

We end up with the following:

Client -> Service -> Domain -> Data

We can replace client, service or data layer with reasonable amount of work. If your domain logic lived in the service and you’ve decided that you want to replace or even remove your service layer, then you’d have to move all of the business logic somewhere else. Such a requirement is rare, but it might happen.

Having said all this, I think this is pretty close to what Martin Fowler meant by saying

These services live on top of the domain model and use the domain
model for data.

Diagram below illustrates this pretty well:



Related Solutions

Default value for UUID column in Postgres

tl;dr Call DEFAULT when defining a column to invoke one of the OSSP uuid functions. The Postgres server will automatically invoke the function every time a row is inserted. CREATE TABLE tbl ( pkey UUID NOT NULL DEFAULT uuid_generate_v1() , CONSTRAINT pkey_tbl...

comparing five integers with if , else if statement

try this : int main () { int n1, n2, n3, n4, n5, biggest,smallest; cout << "Enter the five numbers: "; cin >> n1 >> n2 >> n3 >> n4 >> n5 ; smallest=biggest=n1; if(n2>biggest){ biggest=n2; } if(n2<smallest){ smallest=n2;...

How to play YouTube audio in background/minimised?

Here's a solution using entirely free and open source software. The basic idea is that although YouTube can't play clips in the background, VLC for Android can play clips in the background, so all we need to do is pipe the clip to VLC where we can listen to it...

Why not use “which”? What to use then?

Here is all you never thought you would ever not want to know about it: Summary To get the pathname of an executable in a Bourne-like shell script (there are a few caveats; see below): ls=$(command -v ls) To find out if a given command exists: if command -v...

Split string into Array of Arrays [closed]

If I got correct what you want to receive as a result, then this code would make what you want: extension Array { func chunked(into size: Int) -> [[Element]] { return stride(from: 0, to: self.count, by: size).map { Array(self[$0 ..< Swift.min($0 + size,...

Retrieving n rows per group

Let's start with the basic scenario. If I want to get some number of rows out of a table, I have two main options: ranking functions; or TOP. First, let's consider the whole set from Production.TransactionHistory for a particular ProductID: SELECT...

Don’t understand how my mum’s Gmail account was hacked

IMPORTANT: this is based on data I got from your link, but the server might implement some protection. For example, once it has sent its "silver bullet" against a victim, it might answer with a faked "silver bullet" to the same request, so that anyone...

What is /storage/emulated/0/?

/storage/emulated/0/Download is the actual path to the files. /sdcard/Download is a symlink to the actual path of /storage/emulated/0/Download However, the actual files are located in the filesystem in /data/media, which is then mounted to /storage/emulated/0...

How can I pass a command line argument into a shell script?

The shell command and any arguments to that command appear as numbered shell variables: $0 has the string value of the command itself, something like script, ./script, /home/user/bin/script or whatever. Any arguments appear as "$1", "$2", "$3" and so on. The...

What is pointer to string in C?

argv is an array of pointers pointing to zero terminated c-strings. I painted the following pretty picture to help you visualize something about the pointers. And here is a code example that shows you how an operating system would pass arguments to your...

How do mobile carriers know video resolution over HTTPS connections?

This is an active area of research. I happen to have done some work in this area, so I'll share what I can about the basic idea (this work was with industry partners and I can't share the secret details ๐Ÿ™‚ ). The tl;dr is that it's often possible to identify an...

How do I change the name of my Android device?

To change the hostname (device name) you have to use the terminal (as root): For Eclair (2.1): echo MYNAME > /proc/sys/kernel/hostname For Froyo (2.2): (works also on most 2.3) setprop net.hostname MYNAME Then restart your wi-fi. To see the change, type...

How does reverse SSH tunneling work?

I love explaining this kind of thing through visualization. ๐Ÿ™‚ Think of your SSH connections as tubes. Big tubes. Normally, you'll reach through these tubes to run a shell on a remote computer. The shell runs in a virtual terminal (tty). But you know this part...

Difference between database vs user vs schema

In Oracle, users and schemas are essentially the same thing. You can consider that a user is the account you use to connect to a database, and a schema is the set of objects (tables, views, etc.) that belong to that account. See this post on Stack Overflow:...

What’s the output of this code written in java?

//if you're using Eclipse, press ctrl-shift-f to "beautify" your code and make it easier to read int arr[] = new int[3]; //create a new array containing 3 elements for (int i = 0; i < 3; i++) { arr[i] = i;//assign each successive value of i to an entry in...

How safe are password managers like LastPass?

We should distinguish between offline password managers (like Password Safe) and online password managers (like LastPass). Offline password managers carry relatively little risk. It is true that the saved passwords are a single point of failure. But then, your...