No mundo do desenvolvimento de software, os princípios SOLID nos dizem como organizar funções e dados de forma que nossos códigos:
- Tolerem mudanças
- Sejam fáceis de entender
- Sejam a base de componentes que podem ser usados em muitos sistemas de software
O termo SOLID é um acrônimo para cinco postulados de design, descritos a seguir:
(S) Single Responsability Principle (Princípio da Responsabilidade Única): "Um módulo deve ter um, e apenas um motivo para mudar"
(O) Open/Closed Principle (Princípio Aberto/Fechado): "Um artefato de software deve estar aberto para extensão, mas fechado para modificações"
(L) Liskov Substitution Principle (Princípio de Substituição de Liskov): "Uma classe derivada deve ser substituível por sua classe base"
(I) Interface Segregation Principle (Principio da Segregação de Interface): "Uma classe não deve ser forçada a implementar interfaces e métodos que não utilizará"
(D) Dependency Inversion Principle (Principio da Inversão de Dependência): "Dependa de abstrações e não de implementações"
SOLID e GoLang
O SOLID é pensado para programação orientada a objetos, e é sabido que GoLang não é uma linguagem que adota esse paradigma. Contudo, podemos usar os recursos que ela disponibiliza para atender à metodologia POO. Por exemplo, Go não tem suporte de herança, mas a ideia pode ser compensada com seu suporte de composição. Da mesma forma, um tipo de polimorfismo pode ser criado usando interfaces.
Nesse artigo, o primeiro de uma série de 5, pretendo detalhar o primeiro princípio com exemplos que se aproximam de situações que nos deparamos no dia a dia.
Single Responsibility Principle (SRP)
Já sabemos o que o termo significa, agora é hora de aprender como implementá-lo em GoLang.
Nessa linguagem, poderíamos definir este princípio como “Uma função ou tipo deve ter um e apenas um trabalho, e uma e apenas uma responsabilidade”, vejamos o seguinte código:
Acima, temos uma struct que chamamos de userService. Ela tem duas propriedades: db que é responsável pela comunicação com um banco de dados relacional, e amqpChannel, que viabiliza a comunicação com o serviço de mensageria RabbitMQ.
UserService implementa um método chamado Create. Dentro desse método armazenamos as informações do usuário recebido no banco de dados e, em seguida, publicamos os dados no RabbitMQ.
Percebe-se que a responsabilidade do método Create no userService não é apenas uma, e sim, duas: armazenar uma informação no banco de dados e publicar uma mensagem numa fila do RabbitMQ.
Isso pode levar a vários problemas, como:
- Dificuldade de manutenção: se um dos requisitos mudar, como por exemplo, a forma como os dados do usuário são serializados, você terá que modificar a lógica do método Create, mesmo que isso não tenha nada a ver com a sua responsabilidade principal, que é salvar os dados no banco de dados.
- Dificuldade de teste: como o método Create tem duas responsabilidades diferentes, você terá que criar testes para cada uma delas, o que pode ser difícil e trabalhoso.
- Acoplamento desnecessário: a lógica de publicação dos dados do usuário em uma fila do RabbitMQ é totalmente independente da lógica de salvar esses dados num banco de dados. Misturar essas duas responsabilidades no mesmo método cria um acoplamento desnecessário.
No código a seguir, modificamos a estrutura para respeitar o SRP. Vejam só:
Observe que separamos as responsabilidades em três partes distintas: o repositório UserRepository para persistir o usuário no banco de dados, o publicador UserPublisher para enviar uma mensagem para o RabbitMQ e o serviço UserService que orquestra essas duas operações.
Dessa forma, cada componente é responsável por uma tarefa específica e independente, facilitando a manutenção e evolução do código, além de permitir que cada uma dessas partes possa ser substituída ou aprimorada sem afetar as outras. Por exemplo, se for necessário mudar o banco de dados utilizado, basta substituir o repositório. Se for necessário alterar a forma de comunicação, basta alterar o publicador.
Vale ressaltar que há uma diferença sutil entre executar duas tarefas distintas e delegar a execução delas. No exemplo original do userService.Create, havia a execução de duas operações em um único lugar, violando o princípio da responsabilidade única. Após a refatoração, delegamos as execuções para structs distintas e o método Create ficou responsável apenas por coordenar este fluxo.
Para aplicar o SRP neste exemplo, também acabamos implementando alguns dos outros princípios SOLID:
- O Interface Segregation Principle (ISP): cada interface representa uma única responsabilidade. Tanto o UserRepository quanto o UserPublisher são interfaces que possuem apenas um método, cada um representando uma única responsabilidade.
- O Dependency Inversion Principle (DIP): a struct userService depende de abstrações (interfaces) e não de implementações concretas, ou seja, ela não conhece a implementação específica do UserRepository e do UserPublisher, apenas as interfaces que eles implementam.
- O Open/Closed Principle (OCP): o código está aberto para extensão, uma vez que novos repositórios ou publicadores podem ser facilmente adicionados sem modificar userService.
Nos próximos artigos dessa série trarei uma explicação mais detalhada de cada um deles, com exemplos específicos.
Até mais, galera!
Referências:
SOLID: The First 5 Principles of Object Oriented Design
Clean Coder Blog - The Single Responsibility Principle
Top comments (0)