⬅️ Back to Layers Overview

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?

  1. Testability - Dependencies easily mocked in unit tests
  2. Immutability - Final fields prevent accidental modification
  3. Explicit - Constructor clearly shows all dependencies
  4. 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);
    }
}

⬅️ Back to Layers Overview