O design de um programa reflete as responsabilidades de cada parte dele, sejam arquivos, procedimentos, classes, funções, métodos, módulos ou qualquer outro nome dado para uma unidade dentro dele.
Em um programa orientado a objetos ele reflete as responsabilidades e o relacionamento entre diferentes classes que ao utilizarem recursos como encapsulamento e polimorfismo restringem esse relacionamento, reduzindo a fragilidade e aumentando a flexibilidade. Não importa qual seja o paradigma, não importa qual é o nome da estrutura, o design é a forma como vamos utilizá-las.
Essas relações na orientação a objetos podem ser associações, dependências ou herança.
Na associação temos uma relação "has a", ou seja, temos variáveis de instância que fazem referência para a outra classe ou interface.
Por exemplo, o contrato tem um cliente:
class Contract {
customer: Customer;
}
Na herança temos uma relação "is a", ou seja, uma classe herda de outra classe.
Por exemplo, um financiamento de veículo é um financiamento:
class CarLoan extends Loan {
}
Na dependência temos parâmetros de um método que fazem referência para uma outra classe ou interface.
Por exemplo, um pedido tem um método adicionar produto que recebe um produto, mas internamente só o identificador é armazenado dentro de outra classe, nesse caso as linhas do pedido.
class Order {
code: string;
lines: Line[];
addProduct (product: Product, quantity: number) {
this.lines.push(new Line(product.idProduct, product.price, quantity));
}
}
Conforme vamos amadurecendo o programa, trazendo outras responsabilidades, elas também precisam ser implementadas em algum lugar, por exemplo, se for necessário persistir um pedido no banco de dados, onde seria implementado?
Abaixo temos uma classe que interage com o banco de dados, ela se chama OrderData justamente para não refletir e trazer informações sobre padrões como DAO ou Repository, mas qual é a sua responsabilidade? Basicamente receber uma classe que representa um pedido e interagir com o banco de dados para persisti-lo.
class OrderData {
save (order: Order) {
await connection.query("insert into order (code) values ($1)", [order.code]);
…
}
}
E se fosse necessário receber esse pedido de uma requisição Http para depois persisti-lo? Essa classe iria interagir diretamente com OrderData? Quem seria responsável por interagir com a classe Order para passar as informações da requisição para a instância?
Viu quanta decisão podemos tomar? Dá pra fazer o programa funcionar com ou sem associação, com ou sem herança.
O que guia essas decisões? É o design ou a arquitetura?
Na prática, ambos.
Podemos definir que vamos utilizar o protocolo HTTP por uma questão de padrão e compatibilidade com diversos clientes, podemos utilizar também uma fila para trazer resiliência, podemos persistir em um banco de dados SQL ou NoSQL dependendo da natureza do modelo de dados, podemos utilizar containers em vários cloud providers como AWS ou Azure por questões de portabilidade e custos, decidimos por algum motivo utilizar Java ou Go por performance e experiência, React ou Angular ou Vue por gosto da equipe, existem diversas decisões que considero que são decisões de arquitetura e tem como foco principalmente atender a requisitos não funcionais como performance, segurança, complience ou somente gosto pessoal.
Isso afeta o design? Totalmente.
Cada decisão de arquitetura impõe restrições ou desafios ao design, ainda assim temos muita liberdade para definir quem tem qual responsabilidade dentro de um programa e isso é uma decisão "aberta", que não tem necessariamente "certo ou errado" e por isso é ao mesmo tempo causa insegurança e nos desafia a apostar em um determinado caminho, um determinado jeito de organizar e estruturar o nosso programa.
Quando falamos em Design Patterns, Domain-Driven Design, Clean Architecture, Ports and Adapters, SOLID principles, nada disso tem relação com Java ou Go, nem com Oracle ou MongoDB, nem com HTTP, nem com AMQP. É o simples fato de decidir quem faz o que dentro da estrutura que você criou, e isso é o mais puro design, influenciado pela arquitetura.
Todo programa tem um design e a alternativa a um "bom" design é um "mau" design, não nenhum design.
Lembre-se, nosso desafio além de fazer tudo funcionar é fazer com que no médio e longo prazo seja possível continuar desenvolvendo o programa de forma saudável, em um ritmo sustentável, sem precisar fazer um esforço desnecessário, sem precisar passar semanas corrigindo defeitos ou reescrevendo tudo, um bom design pode proporcionar tudo isso.