laravelbrasil / forum

Ama Laravel? Torne se um Jedi e Ajude outros Padawans
GNU General Public License v3.0
252 stars 13 forks source link

O jeito nativo com que o laravel manipula os models fere algum padrão de codificação conhecido? #41

Open EFJunior opened 7 years ago

EFJunior commented 7 years ago

Olá galera, eu sempre questionei a ausência de "gets e sets" nos models do laravel (pois era o jeito como eu enxergava models antes, de forma bem crua). E isso me faz perguntar se o modo como laravel trata os models fere algum tipo de padrão conhecido. Outra questão que me faz pensar é quando usar o Mass Assignment ou não. Espero que possam esclarecer essas dúvidas. Obrigado desde já.

paulofreitas commented 7 years ago

Esta é uma discussão arquitetural. 😃

Através do Eloquent, seu ORM (de Object-Relational Mapper), o Laravel implementa o padrão Active Record (AR). Outros frameworks como o CakePHP, o Yii e até mesmo o Rails do Ruby seguem esta mesma arquitetura.

Por outro lado, há um outro padrão convergente chamado Data Mapper (DM), que é utilizado por exemplo no Symfony através de seu ORM Doctrine, no Java através do Hibernate e no .Net através do NHibernate. No PHP ainda há ainda o Analogue, uma implementação do Data Mapper baseada no Eloquent.

Para explicar a diferença entre o Active Record e o Data Mapper vou aproveitar aqui o que comentei recentemente no grupo do Facebook a respeito de uma discussão entre o Doctrine e o Eloquent, que é basicamente uma tradução livre do texto original extraído do podcast An ORM discussion do excelente PHP Roundtable.

Características do Active Record:

Características do Data Mapper:

Em resumo:

Note que essa discussão é muito mais relativa à arquitetura do software do que sobre a preferência de um padrão em detrimento do outro. Ambos padrões têm suas vantagens e desvantagens, e a escolha entre um e outro está mais ligada aos requisitos do projeto do que qualquer outra coisa. 😄

Observe que é uma característica do próprio Active Record não se preocupar em mapear os atributos do model - ele é dinâmico por definição. E aqui o conceito de proteção contra mass assignment existe para garantir que ele só armazene os atributos que você permitir. É assim em todos os frameworks que implementam o AR. :+1:

É importante distinguir aqui que o conceito do Data Mapper surgiu através do Domain-Driven Design (DDD), uma arquitetura multi-camada onde a camada do domínio é separada da camada de infraestrutura (onde entra o banco de dados). Neste tipo de arquitetura faz mais sentido você usar o Data Mapper uma vez que, por definição, sua arquitetura deveria ser isolada. Isso também se aplica aos padrões arquiteturais baseados no DDD, como o Hexagonal Architecture. Purismos à parte, não é raro de se encontrar arquiteturas baseadas no DDD que implementam o Active Record de forma decente e particularmente não vejo nenhum problema nisso.

Portanto, respondendo a pergunta inicial, o Laravel não está violando nenhum padrão, ele simplesmente implementa um padrão diferente daquele que você está habituado. 😄

Você é livre para, por exemplo, implementar o Data Mapper através do ORM Doctrine no lugar do Eloquent usando o Laravel Doctrine. Toda mudança tem um preço, e aqui perder toda a mágica do Eloquent é o preço. 😜

Quanto ao mass assignment, essa é uma prática muito comum nos frameworks que implementam o Active Record por mera questão de simplicidade - um dos pilares do padrão. Por padrão no Laravel todo model é protegido contra mass assignment, de modo que você é obrigado a declarar um atributo $fillable ou $guarded em cada model especificando quais campos devem ou não ser atribuídos quando você estiver salvando dados. Porque você é obrigado a especificar isso manualmente em cada model eu não vejo nenhum problema em adotar mass assignment uma vez que isso simplifica o código consideravelmente. :wink:

Espero ter respondido a contento e principalmente não ter esquecido de nada! 😁

Maykonn commented 7 years ago

Eu tive essa mesma dúvida quando comecei estudar Active Record anos atrás quando conheci o Yii1. Eu estava vindo do Zend Framework 1 então pense na quantidade de dúvidas que tive, algumas foram essas (pode acrescentar algo a discussão):

Stackoverflow: Active Record why it add things like save().

Stackoverflow: Active Record must have domain logic?.

Acho que esse tipo de discussão sempre é baseada em opiniões primárias e até mesmo quando pensamos sobre os requisitos de nossos projetos (isso soa abstrato sempre, mas...), podemos nos ver tentados ao gosto pessoal do que analisar o que é melhor para o projeto.

Hoje na grande maioria dos projetos que participo dou preferencia a AR do que ao DM, isso porque a grande maioria dos projetos não possuem um Domain complexo demais (apesar de serem bem bem grandes) e portanto, sendo assim, eu dou preferencia a:

É mais fácil de trabalhar e tem poucas barreiras de ser usado, tendo em contrapartida um custo de ambiguidade entre a lógica de registro e a lógica de objeto (AR)

Do que:

Muitas vezes sacrifica a experiência do desenvolvedor para ganhar pureza arquitetural (DM)

wilcorrea commented 7 years ago

@EduardoFerreiraJunior as respostas acima são perfeitas e acredito que já seria suficiente para esclarecer sua dúvida. Queria apenas dizer que entendo as razões pela qual essas dúvidas foram fomentadas (malditos gets e sets das aulas de técnicos e faculdades) e escrevi um artigo sobre isso em https://goo.gl/WlPLko

paulofreitas commented 7 years ago

@Maykonn Muito obrigado por aprofundar a questão de uso do Active Record em projetos grandes! 😉

Por conta do horário avançado eu acabei não abordando muito, mas em geral eu também sou favorável em sacrificar a pureza do DM pela facilidade de implementação do AR – principalmente no Laravel por conta do Eloquent. Como disse tem muitos projetos baseados na estrutura do DDD/Hexagonal que usam o AR de forma decente e eu particularmente acho que projetos assim, principalmente quando são grandes, já são muito melhores de trabalhar do que aqueles que não se preocupam em seguir uma arquitetura mais robusta. O purismo do DM de fato raramente trará algum ganho real para a aplicação em si. 👍

Quando digo usar o AR de forma decente, falo por exemplo sobre as questões de desacoplar o ORM e abstrair os models através do conceito de uso dos transformers quando necessário, o que é aliás uma maneira bem inteligente de simular o modo que o DM funciona. 😄


Uma coisa que eu esqueci de abordar aqui e que tem espaço pra ser relacionado neste tópico é a velha definição de Anemic Domain Model (ADM) do Martin Fowler.

Tanto o Active Record como o Data Mapper são supostos em ter comportamento (behavior) nos objetos de registro, isto é, a lógica e as regras de negócio do domínio residem principalmente nestes objetos.

Digo principalmente porque isso não implica que tudo deva estar neste objeto em si, já que algumas coisas podem ser delegadas para outros objetos que também se situam na camada de domínio, como por exemplo os objetos de valor (ou value objects). Um exemplo do próprio Laravel são as classes FormRequest que fazem uma separação bem mais robusta dos casos de validação e autorização.

Fowler advoga que um modelo de domínio anêmico de comportamento é um anti-pattern. Eles são supostos em ser ricos de comportamento, isto é, Rich Domain Models (RDM). Você não deve ter comportamento nem regras de negócio em classes de serviço (ou services). Isso, na concepção do Fowler e do Eric Evans (o pai do DDD) está errado. As classes de serviço são supostas a usar os métodos dos próprios objetos de registro e objetos de valor do domínio. Somos supostos a expor todo o comportamento deles dentro do próprio domínio ao invés de maquiar isso fora da camada de domínio.

Note que isso de certa forma implica naquilo que chamamos de fat model (e não god model, este implica em violar o SRP). Não que você deva ter objetos de registro gigantescos e difíceis de dar manutenção. Os traits existem para isso e os fundamentos da separação de conceitos também são sobre isso. O código do próprio Laravel tem sido remodelado para seguir este princípio, como se pode ver aqui e aqui. Esta é por sinal uma excelente maneira de se implementar RDMs sem que isso resulte em classes gigantes e de alguma forma prejudique a manutenção, legibilidade e até mesmo métricas de qualidade de código. 😄

Espero que esse adendo acrescente ainda mais ao tópico, já que de alguma forma está relacionado com a definição do AR e DM. 👍

Maykonn commented 7 years ago

@paulofreitas boa colocação:

Digo principalmente porque isso não implica que tudo deva estar neste objeto em si, já que algumas coisas podem ser delegadas para outros objetos

paulofreitas commented 7 years ago

@Maykonn Hehehe, tem que separar as coisas, né... 😄

Pra quem não está habituado com arquiteturas multi-camadas é mais difícil interpretar domain model como um agregado de classes de um dado domínio (objetos de registro, objetos de valor, especificações, etc.), geralmente as pessoas associam a palavra domain model com um único objeto de registro (que chamamos de model no AR) em específico - eu mesmo fazia essa associação antes de entender exatamente do se tratava. 😆

Através dos conceitos do DDD fica mais fácil entender o posicionamento do Fowler. Aliás, não há exemplo melhor para entender o que ele diz com rich behavior do que aquele projeto modelo do DDD que surgiu escrito em Java: https://github.com/patrikfr/dddsample Tem ótimas derivações em outras linguagens, até mesmo em PHP, mas o exemplo original continua sendo o mais fiel tanto às posições do Fowler quanto ao que o Eric descreve no livro do DDD. É um excelente código para se estudar os princípios do DDD e a aplicabilidade de alguns design patterns! 😀

Analisando as classes da camada de domínio desse projeto modelo fica bem mais simples de entender o que é o tal do comportamento rico e porque a lógica e regras de negócio do domínio não necessariamente residem apenas em objetos de registro. É o exemplo perfeito de código para se aprofundar nos conceitos do DDD! 😃

Maykonn commented 7 years ago

@paulofreitas pois é. Leva um tempo mesmo até as pessoas entenderem que até mesmo no MVC o M ali é uma camada e não um objeto AR. Leva mais tempo ainda para entender que o DDD se aplica no M do MVC da grande maioria dos nossos frameworks (não confundir com o MVC original que Fowler cita em GUI Architectures até porque ele é bem diferente do MVC do Laravel, Yii, Rails, etc).

EFJunior commented 7 years ago

Obrigado a todos, esclareceram minhas dúvidas. Para eu me aprofundar mais no assunto recomendam algum livro ou video que explique melhor o DDD ?

Maykonn commented 7 years ago

@EduardoFerreiraJunior

Lista de bons livros(lembro só desses em português):

paulofreitas commented 7 years ago

@Maykonn Excelentes pontos! 👏

De fato não é raro de ver o MVC ser mal interpretado neste sentido, de que tratam-se de 3 camadas e que o M representa a camada de domínio que, por definição, vai muito além dos objetos de registro. Creio que o que acaba pesando neste ponto é o background de engenharia de software, somente quando nos aprofundamos nas disciplinas de design de software que essas coisas passam a ficar mais claras. Inclusive é neste aprofundamento que fica bem mais fácil de enxergar que o DDD nada mais é do que uma metodologia de design centrada na lógica de negócios do software que por sua vez está no M do MVC. 😃

A separação em camadas evoluiu ao ponto onde os conceitos de repositórios e serviços são usados muitas vezes sem o conhecimento de que vieram do DDD ou ainda do porque serem úteis para desacoplar o código entre as camadas e promover um melhor reuso de código. Se por um lado isso não é exatamente bom, por outro lado mostra que estamos caminhando naturalmente para um ponto onde é cada vez mais comum ter camadas desacopladas e coesas - e isso é particularmente muito bom! 😄

@EduardoFerreiraJunior Os livros citados pelo Maykonn, do Eric Evans e do Vaughn Vernon, são simplesmente as bíblias do DDD! Ambos tu irá encontrar em português, principalmente na Saraiva. Em inglês creio que tu encontre tanto na Livraria Cultura como na Amazon, mas tem um detalhe: por serem importados são bem mais caros. :wink:

Um terceiro que posso recomendar, mais introdutório, é o Domain-Driven Design in PHP. Este infelizmente só tem em formato digital e em inglês. Tem exemplos voltado ao PHP e também aborda um pouco de Hexagonal Architecture. 👍

renanoliveira0 commented 7 years ago

@paulofreitas , como aqui estamos meio que discutindo o assunto, vou colocar uma dúvida então aqui.

Na sua explicação vc passou que o Data Maper é necessário uma implementação de uma abstração ao acesso à dados, que seria implementada com os repositórios, e active record não, certo?

Eu uso repositórios mesmo com o Eloquent, vejo vantagens em centralizar em poucas classes todas as consultas, mesmo que o eloquent já abstraia bastante. Também vejo a necessidade pois futuramente o meu sistema irá evoluir para micro-services, aí eu usaria os repositório ligando em outro app que acessaria os dados propriamente ditos. As afirmações aqui estão corretas?

A necessidade do uso do Unity of Work existe em caso de data mapers?

Grato desde já!

paulofreitas commented 7 years ago

@renanoliveira0 Opa! :)

Na sua explicação vc passou que o Data Maper é necessário uma implementação de uma abstração ao acesso à dados, que seria implementada com os repositórios, e active record não, certo?

Correto, no padrão Data Mapper a persistência é tratada separadamente na camada de repositórios, enquanto no Active Record a persistência é realizada nos próprios objetos de registro. Enquanto no primeiro os repositórios são parte da arquitetura, no segundo é uma separação opcional porém muito interessante de se ter. 👍

Eu uso repositórios mesmo com o Eloquent, vejo vantagens em centralizar em poucas classes todas as consultas, mesmo que o eloquent já abstraia bastante.

Perfeitamente válido, ajuda não apenas a desacoplar as camadas mas reduzir consideravelmente a duplicação de código. 😃

Um ponto polêmico dos repositórios que usam Active Record é que em algum ponto você é suposto a desacoplar o ORM para não expor ele ao retornar os dados - isso é importante porque, caso contrário, você poderia violar a API que você provê através do repositório. Uma técnica que se popularizou e que resolve brilhantemente esta questão é o uso de transformers para fazer a dita serialização dos dados retornados. 👍

Também vejo a necessidade pois futuramente o meu sistema irá evoluir para micro-services, aí eu usaria os repositório ligando em outro app que acessaria os dados propriamente ditos. As afirmações aqui estão corretas?

Perfeitamente válido também. 👍

A necessidade do uso do Unity of Work existe em caso de data mapers?

Geralmente não, note que no caso do Doctrine ele usa o Unit of Work internamente. 😃

renanoliveira0 commented 7 years ago

@paulofreitas , então, estava vendo outro dia sobre esse tópico, parece que é o L do SOLID, certo? O princípio da Liskov sei lá uq, uma professora do MIT que inventou essa parada.

http://stackoverflow.com/questions/22031972/laravel-dependency-injection-when-do-you-have-to-when-can-you-mock-facades-ad

Na primeira resposta nessa pergunta aí o cara sugere um :

return Post::all()->toArray();

isso não resolveria a questão?

paulofreitas commented 7 years ago

@renanoliveira0 Na verdade isso está tanto relacionado ao DIP quanto ao LSP, respectivamente o D e L do SOLID. Por um lado o DIP dita que você deve separar o código de alto nível do código de baixo nível através do desacoplamento por meio de abstrações (interfaces), e paralelo a isso o LSP dita que toda implementação de uma abstração deve ser substituível em qualquer lugar onde a abstração é aceita, de modo que todas implementações devem retornar o mesmo tipo de dado. Mas de fato é o LSP quem implica em abstrair o tipo de dado retornado. :wink:

Note no entanto que essa questão vai um pouco além da ótica do SOLID.

Digamos que você só irá implementar repositórios do Eloquent e que portanto não precisará ter interfaces - isso é algo muito comum, e aqui esse tipo de violação do SOLID é perfeitamente aceitável. Ocorre que, por conta disso, muita gente se esquece que ainda assim você precisa abstrair o retorno dos dados, mesmo que você não tenha interfaces ou outras implementações.

Porque? Pelo simples fato de que, se você não fizer isso, você estará violando o desacoplamento que era o objetivo do repositório e ainda a API fornecida por ele. Posto que o Eloquent é baseado no Active Record, retornar os objetos de registro diretamente significa que você poderá manipular informações no banco fora do repositório, quebrando o desacoplamento e violando o que você definiu na API dele. 👍

De modo geral o método toArray() dos models do Eloquent também podem resolver essa questão, mas é válido lembrar que isso é uma característica do Eloquent ORM. Os transformers são uma especialização desta serialização do mesmo modo que os presenters são uma especialização da formatação para apresentação, de modo que você consegue separar melhor as responsabilidades e comportamentos através deles. E, mais importante que isso, eles ajudam a tratar da serialização independente da origem dos dados, seja um model do Eloquent, uma entity do Doctrine, dados de uma API externa, etc., sendo portanto uma camada que uniformiza esse tratamento dos dados. :wink:

renanoliveira0 commented 7 years ago

@paulofreitas, excelente explicação bicho, vou dar uma olhada melhor nesse transformer depois aí... Desde já muito obrigado!