Early Access: 87 spots left.

Claim
Low Level DesignDependency InversionDependency Inversion: Inject the OrderRepository

Course content

Dependency Inversion: Inject the OrderRepository

Medium·Tagssoliddependency-inversiondependency-injectionrefactoring

Problem Statement

The starter has an `OrderService` that internally constructs a `MySqlOrderRepository`: ```java class OrderService { private final MySqlOrderRepository repo; public OrderService() { this.repo = new MySqlOrderRepository(); } public void placeOrder(String orderId, double amount) { if (amount <= 0) { throw new IllegalArgumentException("Order amount must be positive"); } repo.save(orderId, amount); } } ``` This violates the Dependency Inversion Principle. The high-level `OrderService` (business logic) directly depends on the low-level `MySqlOrderRepository` (data access). The dependency is hardcoded and the field is typed against the concrete class, so swapping the repository for a different implementation, or for a fake in tests, requires editing `OrderService`. Refactor the design so that `OrderService` depends on an abstraction and receives its repository through its constructor. The starter already declares an empty `OrderRepository` interface for you to populate. After your refactor, the design must expose this exact public API: ```java interface OrderRepository { void save(String orderId, double amount); } class MySqlOrderRepository implements OrderRepository { public void save(String orderId, double amount) { ... } } class OrderService { public OrderService(OrderRepository repo); // No no-arg constructor. The repository must be injected. public void placeOrder(String orderId, double amount); // Validates amount, then delegates to repo.save. } ``` The validator runs five checks: 1. `OrderService` actually uses the repository it was given. The validator constructs its own `OrderRepository` implementation, passes it into `OrderService`, calls `placeOrder`, and asserts the validator's repository received the save call with the right arguments. If `OrderService` is still constructing its own `MySqlOrderRepository` internally, the injected repository never sees the call and this check fails. 2. `OrderService` has a constructor that accepts an `OrderRepository`. Reflection check. 3. `OrderService` does not have a no-argument constructor. Reflection check. This forces constructor injection: callers must hand in a repository, they cannot rely on `OrderService` to allocate one. 4. `MySqlOrderRepository` implements the `OrderRepository` interface. Reflection check. 5. The `OrderRepository` interface declares `save(String, double)`. Reflection check, mostly a smoke test for the interface itself. The smelly starter passes none of these out of the box. Each requires the actual refactor.

Examples

Example 1
Input
OrderService svc = new OrderService(new MySqlOrderRepository()); svc.placeOrder("order-1", 250.0)
Output
"(saves order-1 / 250.0 to MySQL)"
Why
OrderService receives its repository through the constructor. In production, callers wire in the MySQL implementation.
Example 2
Input
OrderService svc = new OrderService(new InMemoryOrderRepository()); svc.placeOrder("order-2", 50.0)
Output
"(saves to whatever in-memory store the test harness passed in)"
Why
Tests can substitute any OrderRepository implementation. The high-level OrderService does not know or care which one.
Example 3
Input
new OrderService()
Output
"compile error after the refactor"
Why
The no-arg constructor is gone. The compiler now insists that the dependency be provided, which is exactly what dependency inversion is for.

Constraints

  • Refactor must keep the three declarations named: OrderRepository, MySqlOrderRepository, OrderService.
  • OrderRepository must be an interface declaring void save(String orderId, double amount).
  • MySqlOrderRepository must implement OrderRepository.
  • OrderService must have a constructor that accepts OrderRepository and store it in a field.
  • OrderService must not have a no-argument constructor.
  • OrderService.placeOrder must throw IllegalArgumentException for non-positive amounts and otherwise delegate to repo.save.

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Start by adding the save method to the OrderRepository interface: `void save(String orderId, double amount);`. Without it, neither MySqlOrderRepository nor the validator's recording repository can implement the interface.
Hint 2
Make MySqlOrderRepository implement OrderRepository. Add `implements OrderRepository` to its declaration and an `@Override` annotation on save.
Hint 3
Replace the no-arg OrderService constructor with one that takes an OrderRepository: `public OrderService(OrderRepository repo) { this.repo = repo; }`. Delete the no-arg version.
Hint 4
Change the type of the OrderService field from MySqlOrderRepository to OrderRepository. The point is to depend on the abstraction.
Hint 5
If you want OrderService to retain a fallback default for production, do it from the call site, not from inside OrderService. The class itself should never allocate its own repository.