Service Layer
Why a Service Layer?
In a Spring Boot application, the service layer acts as the business brain. The controller handles HTTP requests, the repository accesses the database, but the main business logic lives in the service layer.
Without a service layer, the controller becomes too fat and hard to maintain.
@Service Annotation
Spring provides a stereotype annotation to mark a class as a service:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
}
The @Service annotation is a specialization of @Component. Functionally identical, but with clearer semantic meaning.
Interface vs Implementation
Using an interface separates the contract from the implementation:
public interface UserService {
List<User> getAllUsers();
User getUserById(Long id);
User createUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public List<User> getAllUsers() {
return userRepository.findAll();
}
@Override
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@Override
public User createUser(User user) {
return userRepository.save(user);
}
}
Benefits of an interface:
| Benefit | Description |
|---|---|
| Decoupling | The controller doesn't know the implementation details |
| Testing | Easy to create a mock implementation |
| Strategy swapping | Change the implementation without changing the caller |
| Multiple impls | You can have UserServiceImpl and UserMockService |
Delegation from Controller to Service
A controller should only receive the request, call the service, and return the response:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User created = userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
Transactions in the Service Layer
@Transactional belongs in the service layer, because the service coordinates multiple operations:
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
public OrderServiceImpl(OrderRepository orderRepository,
ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
}
@Override
@Transactional
public Order createOrder(Order order) {
Product product = productRepository.findById(order.getProductId())
.orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() < order.getQuantity()) {
throw new RuntimeException("Insufficient stock");
}
product.setStock(product.getStock() - order.getQuantity());
productRepository.save(product);
return orderRepository.save(order);
}
@Override
@Transactional(readOnly = true)
public List<Order> getAllOrders() {
return orderRepository.findAll();
}
}
When @Transactional is placed at the class level, all public methods become transactional automatically. Use readOnly = true on queries for better performance.
Clean Architecture
A well-organized layering in a Spring Boot application:
Controller → Service → Repository → Database
↓ ↓
DTO Entity/Domain
The basic rules:
- Controller only receives requests and calls the service
- Service contains the business logic and transaction coordination
- Repository only interacts with the database
- Entity represents the data
- DTO for transferring data between layers
With a well-structured service layer, the code becomes easier to test, maintain, and extend. In the next chapter, we'll learn about the DTO Pattern for separating data representation between layers.