Reading.CleanArchitecture.VideoSales

This chapter presents a case study that applies Clean Architecture principles to a real-world example: a system for managing video sales. Robert C. Martin uses this example to demonstrate how to organize a system’s architecture to separate business rules, use cases, and frameworks effectively. The case study illustrates how Clean Architecture principles are implemented in practice.


Overview of the Video Sales System

Purpose of the System

  • The system is designed to manage the sales of videos.
  • It includes features like:
    • Tracking customers and their purchases.
    • Offering promotional discounts.
    • Generating invoices.

Architecture Design

1. Core Requirements

  • Business Logic:
    • Calculate the total cost of a purchase, including discounts and taxes.
  • Use Cases:
    • Customers can purchase videos.
    • Generate invoices for purchases.
  • External Systems:
    • Integration with a database to store customer and video information.
    • A web interface for users to interact with the system.

2. Clean Architecture Layers

  • Entities (Core Business Rules):
    • Define the domain objects (e.g., Customer, Video, Purchase).
    • Business rules like calculating purchase totals reside here.
  • Use Cases (Application Layer):
    • Implement workflows (e.g., PurchaseVideoUseCase).
  • Interface Adapters:
    • Translate between external systems (e.g., web UI, database) and core logic.
  • Frameworks and Drivers:
    • Handle HTTP requests, database access, and other external concerns.

Detailed Implementation

Entities Layer (Core Business Rules)

  • Customer Entity:
    • Tracks a customer’s purchase history and total spending.
public class Customer {
    private String name;
    private double totalSpent;

    public void addPurchase(double amount) {
        totalSpent += amount;
    }

    public double getTotalSpent() {
        return totalSpent;
    }
}
  • Video Entity:
    • Represents a video available for purchase.




public class Video {
    private String title;
    private double price;

    public double getPrice() {
        return price;
    }
}

Purchase Entity:

  • Calculates the total cost of a purchase.
public class Purchase {
    private List<Video> videos;
    private double discount;

    public double calculateTotal() {
        double total = videos.stream().mapToDouble(Video::getPrice).sum();
        return total - discount;
    }
}

Use Cases Layer

  • Implements workflows for video sales.

PurchaseVideoUseCase:

  • Handles the purchase process, applying business rules and interacting with repositories.
public class PurchaseVideoUseCase {
    private CustomerRepository customerRepository;
    private VideoRepository videoRepository;

    public PurchaseVideoUseCase(CustomerRepository customerRepository, VideoRepository videoRepository) {
        this.customerRepository = customerRepository;
        this.videoRepository = videoRepository;
    }

    public void execute(int customerId, List<Integer> videoIds, double discount) {
        Customer customer = customerRepository.findById(customerId);
        List<Video> videos = videoRepository.findByIds(videoIds);

        Purchase purchase = new Purchase(videos, discount);
        customer.addPurchase(purchase.calculateTotal());

        customerRepository.save(customer);
    }
}

Interface Adapters

  • Translates between the external interface (e.g., web requests) and the core use cases.

Controller:

@RestController
public class VideoController {
    private PurchaseVideoUseCase purchaseVideoUseCase;

    public VideoController(PurchaseVideoUseCase purchaseVideoUseCase) {
        this.purchaseVideoUseCase = purchaseVideoUseCase;
    }

    @PostMapping("/purchase")
    public ResponseEntity<String> purchaseVideos(@RequestBody PurchaseRequest request) {
        purchaseVideoUseCase.execute(request.getCustomerId(), request.getVideoIds(), request.getDiscount());
        return ResponseEntity.ok("Purchase successful");
    }
}

Frameworks and Drivers

  • Contains implementation details like database interaction and web framework configuration.

Repositories:

  • CustomerRepository:
public interface CustomerRepository {
    Customer findById(int id);
    void save(Customer customer);
}

public class SqlCustomerRepository implements CustomerRepository {
    @Override
    public Customer findById(int id) {
        // SQL logic to fetch customer
    }

    @Override
    public void save(Customer customer) {
        // SQL logic to save customer
    }
}

VideoRepository:

public interface VideoRepository {
    List<Video> findByIds(List<Integer> ids);
}

public class SqlVideoRepository implements VideoRepository {
    @Override
    public List<Video> findByIds(List<Integer> ids) {
        // SQL logic to fetch videos
    }
}

Benefits of the Clean Architecture in This Case Study

1. Flexibility

  • The business logic is independent of the web interface and database.
  • You can replace the web interface (e.g., with a mobile app) without modifying the core logic.

2. Testability

  • Core logic and use cases can be tested without relying on a web server or database.
  • Example: Mock repositories to test the PurchaseVideoUseCase.

Test:

@Test
public void testPurchaseVideoUseCase() {
    CustomerRepository mockCustomerRepo = Mockito.mock(CustomerRepository.class);
    VideoRepository mockVideoRepo = Mockito.mock(VideoRepository.class);

    Customer customer = new Customer("John Doe");
    Mockito.when(mockCustomerRepo.findById(1)).thenReturn(customer);

    Video video = new Video("Clean Architecture", 50.0);
    Mockito.when(mockVideoRepo.findByIds(Arrays.asList(101))).thenReturn(Collections.singletonList(video));

    PurchaseVideoUseCase useCase = new PurchaseVideoUseCase(mockCustomerRepo, mockVideoRepo);
    useCase.execute(1, Arrays.asList(101), 10.0);

    Mockito.verify(mockCustomerRepo).save(customer);
    assertEquals(40.0, customer.getTotalSpent(), 0.01);
}

3. Maintainability

  • Business rules (e.g., discount calculations) are centralized in the core logic, making them easier to modify or extend.
  • Framework dependencies are isolated, reducing the impact of technology changes.

Key Takeaways

  1. Core Logic Independence:
    • Business rules and workflows are isolated from web frameworks and database technologies.
  2. Layered Approach:
    • Separate business rules, use cases, and infrastructure concerns into distinct layers.
  3. Ease of Testing:
    • Core logic is easily testable with mock implementations of external dependencies.
  4. Framework as a Tool:
    • Frameworks and databases are treated as details, confined to the outermost layers.
This entry was posted in Без рубрики. Bookmark the permalink.