Refactoring `CalculadoraAdapter` For Better Responsibility
Let's dive into refactoring the CalculadoraAdapter class, guys! The goal here is to make the code cleaner, more maintainable, and easier to understand. Currently, the calculadoraRequestToCalculadora method is doing too much. It's handling the creation of Percentual and Bonus objects, fetching the Loja (store) and updating both the Calculadora and Loja entities. This violates the Single Responsibility Principle, making the code harder to test and reason about. We will be optimizing the following method:
@Override
public Calculadora calculadoraRequestToCalculadora(Calculadora calculadora, CalculadoraRequest request) {
var loja = lojaService.buscarLoja(request.lojaId());
var percentual = Percentual.builder()
.percentualPrimeiroColocado(request.percentualPrimeiroColocado())
.percentualSegundoColocado(request.percentualSegundoColocado())
.percentualTerceiroColocado(request.percentualTerceiroColocado())
.percentualDemaisColocados(request.percentualDemaisColocados())
.build();
var bonus = Bonus.builder()
.bonusPrimeiroColocado(request.bonusPrimeiroColocado())
.bonusSegundoColocado(request.bonusSegundoColocado())
.bonusTerceiroColocado(request.bonusTerceiroColocado())
.build();
calculadora.atualizar(request.nome(), percentual, bonus, loja);
loja.setCalculadora(calculadora);
return calculadora;
}
Why Refactor?
The calculadoraRequestToCalculadora method is responsible for too many things, which makes it hard to maintain and test. By shifting some of its responsibilities to the service layer, we make the code cleaner and easier to understand. Here’s a detailed explanation:
- Single Responsibility Principle (SRP): The current method violates SRP because it handles multiple unrelated tasks such as fetching store data, creating value objects (
PercentualandBonus), updating the calculator, and associating the calculator with the store. Each method or class should have one responsibility and one reason to change. When responsibilities are mixed, any change to one part of the method can potentially affect other unrelated parts, leading to unexpected bugs and increased complexity. - Testability: A method with multiple responsibilities is harder to test because each responsibility requires its own set of test cases. When the method is broken down into smaller, single-purpose methods, each can be tested in isolation, making the testing process simpler and more thorough. This also ensures that any changes or bug fixes are easier to validate.
- Readability and Maintainability: The current method is lengthy and complex, making it harder to read and understand. By delegating responsibilities to the service layer, the adapter method becomes simpler and more focused, improving code readability and making it easier for developers to maintain. Clear, concise code reduces the likelihood of introducing errors and makes debugging more efficient.
- Code Reusability: Moving some of the logic to the service layer allows that logic to be reused in other parts of the application. For example, the logic for creating
PercentualandBonusobjects might be needed in other contexts. By centralizing this logic in the service layer, it can be easily reused without duplicating code. - Layered Architecture: Following a layered architecture helps in separating concerns and responsibilities. The adapter layer should primarily focus on adapting requests to the domain model, while the service layer should handle the business logic. This separation makes the application more modular and easier to evolve.
Moving Responsibilities to the Service Layer
The core idea is to offload tasks like fetching the Loja and creating the Percentual and Bonus objects to the service layer. This simplifies the adapter method and makes it more focused on its primary responsibility: adapting the request to the Calculadora entity.
Let's outline a step-by-step approach to refactor this method effectively:
- Create Service Methods: Introduce new methods in the service layer to handle the creation of
PercentualandBonusobjects, as well as fetching theLoja. - Delegate Tasks: Modify the adapter method to call these new service methods instead of handling these tasks directly.
- Update the Adapter: Refactor the adapter method to only focus on adapting the incoming request to the
Calculadoraentity.
Step-by-Step Refactoring
Here’s how we can refactor the code:
- Create Service Methods:
First, let's add the necessary methods to the service layer. Assume we have a CalculadoraService class. We’ll add methods to fetch the Loja, create Percentual, and create Bonus objects.
@Service
public class CalculadoraService {
@Autowired
private LojaService lojaService;
public Loja buscarLoja(Long lojaId) {
return lojaService.buscarLoja(lojaId);
}
public Percentual criarPercentual(CalculadoraRequest request) {
return Percentual.builder()
.percentualPrimeiroColocado(request.percentualPrimeiroColocado())
.percentualSegundoColocado(request.percentualSegundoColocado())
.percentualTerceiroColocado(request.percentualTerceiroColocado())
.percentualDemaisColocados(request.percentualDemaisColocados())
.build();
}
public Bonus criarBonus(CalculadoraRequest request) {
return Bonus.builder()
.bonusPrimeiroColocado(request.bonusPrimeiroColocado())
.bonusSegundoColocado(request.bonusSegundoColocado())
.bonusTerceiroColocado(request.bonusTerceiroColocado())
.build();
}
}
- Delegate Tasks:
Now, let's modify the CalculadoraAdapter to use these service methods. This will significantly reduce the responsibilities of the adapter method.
@Component
public class CalculadoraAdapter {
@Autowired
private CalculadoraService calculadoraService;
@Override
public Calculadora calculadoraRequestToCalculadora(Calculadora calculadora, CalculadoraRequest request) {
Loja loja = calculadoraService.buscarLoja(request.lojaId());
Percentual percentual = calculadoraService.criarPercentual(request);
Bonus bonus = calculadoraService.criarBonus(request);
calculadora.atualizar(request.nome(), percentual, bonus, loja);
loja.setCalculadora(calculadora);
return calculadora;
}
}
Benefits of the Refactoring
After these changes, the calculadoraRequestToCalculadora method becomes much cleaner and easier to manage. The responsibilities are now correctly separated, with the adapter focusing on adapting the request and the service layer handling the business logic.
By moving the creation of Percentual and Bonus objects to the service layer, we've not only simplified the adapter method but also made the code more reusable. Other parts of the application can now use these service methods to create Percentual and Bonus objects as needed, without duplicating the logic. This promotes consistency and reduces the risk of errors.
Final Thoughts
Refactoring is an ongoing process, and it's important to continually evaluate your code to identify areas for improvement. By applying principles like the Single Responsibility Principle and keeping a close eye on code complexity, you can ensure that your codebase remains maintainable and easy to understand.
In conclusion, refactoring the CalculadoraAdapter by moving responsibilities to the service layer significantly improves the code's structure, readability, and maintainability. It aligns with best practices for layered architecture and promotes code reuse, resulting in a more robust and scalable application. Remember, clean code is happy code!
For more information on best practices in software development and refactoring, check out Martin Fowler's Refactoring book.