Reading.CleanArchitecture.Services

Key Concepts of Services: Great and Small

1. What Are Services?

  • A service is any software component that performs a specific function or provides a set of behaviors to other components.
  • Services can exist at various levels within the architecture, ranging from small, narrowly scoped functions to large, multi-faceted systems.


2. Types of Services

Martin categorizes services based on their scope and purpose:

a. Business Services
  • Represent high-level business operations or workflows.
  • Often implemented as use cases in the application layer.
  • Example: PlaceOrderUseCase orchestrates actions like validating an order, calculating totals, and saving the order.
b. Application Services
  • Handle application-specific tasks that don’t belong to the domain layer but are critical to the system’s operation.
  • Example: A service that sends confirmation emails or logs application events.
c. System Services
  • Provide low-level, infrastructure-specific functionality.
  • Example: Database access, file I/O, network communication.

3. Services in Clean Architecture

  • Placement in Layers:
    • Business services (use cases) belong in the application layer.
    • Application services and system services belong in the interface adapter or framework and driver layers.
  • Dependency Rule:
    • High-level services (business logic) should not depend on low-level system services. Instead, system services should be abstracted behind interfaces.

4. Characteristics of Good Services

a. Single Responsibility
  • A service should have one and only one reason to change.
  • This aligns with the Single Responsibility Principle (SRP).
b. Testability
  • Services should be designed so that their behavior can be tested independently of other components.
c. Independence
  • Services should not tightly couple themselves to frameworks, databases, or other infrastructure elements.
  • Dependencies should be injected rather than directly instantiated.
d. Reusability
  • Properly designed services are reusable across different parts of the application or even different applications.

5. Avoiding Common Pitfalls with Services

a. Overloading Services
  • Don’t create “God services” that handle too many responsibilities. For example, a UserService that manages everything from authentication to user data retrieval violates SRP.
b. Overusing Services
  • Avoid wrapping everything in a service when it’s unnecessary. Some logic is better encapsulated in entities or value objects.
c. Misplacing Business Logic
  • Business logic should reside in the domain layer or application layer, not in system services or controllers.

6. Examples of Services in Practice

Business Service Example (Use Case):
  • A service to handle order placement:javaCopyEdit
public class PlaceOrderService {
    private OrderRepository orderRepository;
    private PaymentGateway paymentGateway;

    public PlaceOrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
    }

    public void placeOrder(Order order) {
        if (order.isValid()) {
            paymentGateway.process(order);
            orderRepository.save(order);
        } else {
            throw new IllegalArgumentException("Invalid order");
        }
    }
}
Application Service Example:
  • A service to send confirmation emails:
public class EmailService {
    public void sendOrderConfirmation(String email, Order order) {
        // Implementation for sending email
    }
}
System Service Example:
  • A service to handle database interactions
public class SqlOrderRepository implements OrderRepository {
    public void save(Order order) {
        // SQL logic to save the order
    }
}

7. Testing Services

  • Unit Tests:
    • Test each service in isolation, mocking dependencies like repositories or external APIs.
  • Integration Tests:
    • Test how services interact with external systems (e.g., databases, third-party APIs).
  • Example
@Test
public void testPlaceOrder() {
    OrderRepository mockRepository = Mockito.mock(OrderRepository.class);
    PaymentGateway mockPaymentGateway = Mockito.mock(PaymentGateway.class);

    PlaceOrderService service = new PlaceOrderService(mockRepository, mockPaymentGateway);

    Order order = new Order(...);
    service.placeOrder(order);

    Mockito.verify(mockPaymentGateway).process(order);
    Mockito.verify(mockRepository).save(order);
}

Key Takeaways

  1. Categorize Services:
    • Differentiate between business, application, and system services, and place them in the appropriate architectural layers.
  2. Follow SRP:
    • Ensure that each service has a single responsibility to make it maintainable and testable.
  3. Abstract System Services:
    • Use interfaces to abstract low-level system services, ensuring independence of high-level business logic.
  4. Avoid Overengineering:
    • Don’t create unnecessary services. Keep logic where it belongs (e.g., domain or application layer).
  5. Design for Testability:
    • Use dependency injection to make services easy to test in isolation.
This entry was posted in Без рубрики. Bookmark the permalink.