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.