Spring has become extremely popular among Java developers. After all, it's a great project with tons of useful features. I decided to share with you a list of practices I use to work effectively on Spring applications, avoid unnecessary framework coupling and achieve a tidy application architecture. The whole set of practices was too long for one article so I divided it into smaller parts. In this one, I will focus on services.
What is NOT a (Good) Service?
If you've read my other texts, you might have noticed that I'm mostly targetting bad practices present in many applications. This one is no exception. It's not uncommon for a Spring application to contain a class like this:
@Service
public class MyObjectService {
@Autowired
private MyObjectRepository myObjectRepository;
public MyObject findOne(Long id) {
return myObjectRepository.findOne(id);
}
public List<MyObject> findAll() {
return myObjectRepository.findAll();
}
public MyObject save(MyObject myObject) {
return myObjectRepository.save(myObject);
}
public void delete(MyObject myObject) {
myObjectRepository.delete(myObject);
}
}
Then, every other method related to MyObject
we can think of, goes into this service. And, of course, every business object type gets its own service like this. Once the application grows beyond basic CRUD, people try to make use of their "nice" services together and end up mixing them into spaghetti:
I could write a lot about why it's a bad idea, but I'll leave that to you and focus on the correct approach instead.
What is a (Good) Service?
A service is a class representing a single application use case or a part of it. Therefore, we say that it contains application specific business rules (think Interactors in The Clean Architecture). For the most part, it creates and orchestrates other objects to fulfill use case's requirements. Service classes belong to the "business part" of the application. This means they are free of Spring dependencies (but still are a very important part of Spring applications).
How to Name a Service?
By the name of the use case, of course! If you're implementing the process of placing an order for an online shop, then name it PlaceOrder
or OrderPlacement
. Anything that describes the process behind the use case, and only it!
There's a great gist about naming service objects in Ruby, that applies to Spring services as well, here (read it, seriously!).
Also, there are certain words we want to avoid like "Service
" or "Manager
". These words add no value and work like a magnet. Imagine what could happen if we named the order placement service an OrderService
. What do you think, where will cancelling an order go? Where will seeing order details go? All OrderService
or OrderManager
says about the class is that it contains something related to order
s. A name has to be descriptive and precise.
Since services are near the boundary of the "business part", they should use simple data structures as their inputs and outputs. A map of String
s, a class with public
fields or private
fields with accessors might work. The reason for this is that we want to have all business rules closed inside the business component. Therefore, domain objects should not leak outside. It is service's responsibility to convert the data structure to domain objects and backwards (of course, it might use a helper class for this if needed). This idea has its roots in Hexagonal architecture and The Clean Architecture.
Stateful or Stateless?
It depends. In most cases, a stateless singleton bean is enough. If it isn't, there's nothing wrong with making it stateful. I've seen a few applications organized around only stateless services/beans and, in some cases, it works really badly. If the obvious implementation of the service is stateless, so be it. But once it begs for some state, e.g., many arguments are passed down through service's methods, you should make it stateful. BTW. This applies to all beans, not just services.
How to Create a Service Instance?
Since I'm assuming that business classes have no knowledge of the framework, then obviously I can't use any Spring annotation like @Service
or @Component
. But there are a few other options:
new
in the controller - might seem like some nasty coupling, but could be enough in some cases, e.g., a simple service with no dependencies @Bean
inside a @Configuration
class in the main component - for stateless services - Factory class annotated with
@Component
, used inside the controller - for stateful services
When creating a service, we inject dependencies using a constructor or a bunch of setter methods. Apart from what many people say, I think there isn't much advantage of using constructor injection instead of setters, especially if you have a lot of dependencies. On the other hand, a lot of dependencies might indicate a problem with the design.
Example
Let's have a more detailed look at the order placement example. We'll assume that processing a new order consists of a few steps:
- Saving the order
- Creating an invoice, associated with the order
- Sending the invoice to the customer
- Sending a parcel to the customer
Of course, our service does not have to implement all of these things by itself. Instead, it will orchestrate its collaborators:
This diagram might (make it) seem complicated, but the code could be very simple:
public class OrderPlacement {
public void execute(OrderData orderData) {
Order order = orderRepository.save(toOrder(orderData));
Invoice invoice = invoiceRepository.save(invoiceFactory.make(order));
mailer.send(invoice);
parcelSender.send(parcelFactory.make(order));
}
}
The service seems fine with being stateless, so we can create it as a singleton bean in a @Configuration
class:
@Configuration
public class OrderServices {
@Bean
public OrderPlacement orderPlacement() {
return new OrderPlacement(...);
}
}
As the last step, we autowire the bean in our controller:
@Controller
public class OrderController {
private OrderPlacement orderPlacement;
@Autowired
public OrderController(OrderPlacement orderPlacement) {
this.orderPlacement = orderPlacement;
}
public void placeOrder(PlaceOrderRequest request) {
orderPlacement.execute(toOrderData(request));
}
}
Please note that the controller can, but doesn't have to have the same input as the service.
Partitioning
We can imagine that there's much more to the process of placing an order than just these few steps. And, of course, each of these might be much more complicated, e.g., it might require more operations, logging or updating business metrics. In such case, it would be way too much to implement in a single service (or not, you've read the gist I linked before?). We could fix that by splitting the process into smaller services, that will become direct collaborators of the original service:
An obvious benefit of (and another reason to perform) such partitioning is that the smaller services are likely to be reusable.
Conclusion
Services are about use cases. They orchestrate other objects/services in order to fulfill requirements behind a use case. A good name for a service is the name of the process behind the (part of) use case it represents. A service consumes and produces simple data structures, thus protecting the business logic from leaking outside the boundaries. It can be either stateful or stateless, which will impact the way we create it. Once we find out that our service is too big or we want to reuse a part of it, we can split the service into smaller ones.
Other parts of the series:
Tidy Spring - Starting a Project
Tidy Spring - Configuration Properties