Dependency Injection
Pattern Overview
Dependency Injection (DI) is used to manage service dependencies. All dependencies are injected via constructor to ensure testability and explicit dependency declaration.
Implementation Pattern
Using Lombok’s @RequiredArgsConstructor
annotation automatically generates a constructor for all final
fields:
@Service
@RequiredArgsConstructor // Lombok generates constructor
public class SupplierServiceImpl implements SupplierService {
private final SupplierRepository repository;
private final SupplierValidator validator;
private final SupplierMapper mapper;
// Constructor auto-generated by Lombok:
// public SupplierServiceImpl(SupplierRepository repository,
// SupplierValidator validator,
// SupplierMapper mapper)
}Why Constructor Injection?
- Testability - Dependencies easily mocked in unit tests
- Immutability - Final fields prevent accidental modification
- Explicit - Constructor clearly shows all dependencies
- Simplicity - @RequiredArgsConstructor eliminates boilerplate
Example Usage
@Service
@RequiredArgsConstructor
public class InventoryItemServiceImpl implements InventoryItemService {
private final InventoryItemRepository repository;
private final SupplierRepository supplierRepository;
private final StockHistoryService stockHistoryService;
public InventoryItemDTO create(CreateInventoryItemDTO dto) {
// All dependencies available for use
Supplier supplier = supplierRepository.findById(dto.getSupplierId())
.orElseThrow(() -> new NoSuchElementException("Supplier not found"));
InventoryItem item = new InventoryItem();
item.setSupplier(supplier);
InventoryItem saved = repository.save(item);
stockHistoryService.logInitialStock(saved);
return mapper.toDTO(saved);
}
}Anti-Pattern: Manual Instantiation
// ❌ Bad - Hard-coded dependency
@Service
public class SupplierServiceImpl implements SupplierService {
private SupplierRepository repository = new SupplierRepository(); // Cannot test
public SupplierDTO create(CreateSupplierDTO dto) {
return mapper.toDTO(repository.save(mapper.toEntity(dto)));
}
}Testing with DI
Constructor injection makes unit testing straightforward:
@ExtendWith(MockitoExtension.class)
class SupplierServiceImplTest {
@Mock
private SupplierRepository repository;
@Mock
private SupplierValidator validator;
@InjectMocks
private SupplierServiceImpl service;
@Test
void testCreate() {
// Arrange
when(repository.save(any())).thenReturn(new Supplier(...));
// Act
SupplierDTO result = service.create(new CreateSupplierDTO(...));
// Assert
assertNotNull(result);
}
}