Service Layer & Business Logic
The Service Layer sits between controllers and repositories. It contains all business logic, transaction management, and orchestration between multiple repositories. This separation keeps controllers thin and logic reusable.
1 Service Interface + Implementation
Java — UserService Interface
public interface UserService {
Page<UserDto> findAll(int page, int size);
UserDto findById(Long id);
UserDto create(CreateUserRequest req);
UserDto update(Long id, UpdateUserRequest req);
void delete(Long id);
UserDto findByEmail(String email);
}
Java — UserServiceImpl.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true) // Default: read-only for all methods
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
@Override
public Page<UserDto> findAll(int page, int size) {
Pageable p = PageRequest.of(page, size, Sort.by("createdAt").descending());
return userRepository.findAll(p).map(userMapper::toDto);
}
@Override
public UserDto findById(Long id) {
return userRepository.findById(id)
.map(userMapper::toDto)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
}
@Override
@Transactional // Override: write transaction for this method
public UserDto create(CreateUserRequest req) {
if (userRepository.existsByEmail(req.getEmail())) {
throw new ConflictException("Email already registered: " + req.getEmail());
}
User user = userMapper.toEntity(req);
user.setPasswordHash(passwordEncoder.encode(req.getPassword()));
return userMapper.toDto(userRepository.save(user));
}
@Override
@Transactional
public void delete(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
userRepository.delete(user);
}
}
2 DTO Pattern with MapStruct
Java — UserMapper with MapStruct
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {
@Mapping(target = "passwordHash", ignore = true)
User toEntity(CreateUserRequest req);
@Mapping(source = "active", target = "isActive")
UserDto toDto(User user);
List<UserDto> toDtoList(List<User> users);
}
// UserDto.java (Lombok)
@Data
@Builder
public class UserDto {
private Long id;
private String name;
private String email;
private Role role;
private boolean isActive;
private LocalDateTime createdAt;
}
3 Code Challenge
Challenge: Build a
ProductService that validates stock before allowing a purchase, updates the stock quantity atomically using @Transactional, and throws a custom InsufficientStockException if stock goes below zero.