artesaos / content-request

Este reposiório visa facilitar a indexação de conteúdo gerado pela comunidade Laravel Brasil.
51 stars 3 forks source link

Boas práticas Models, Repositories, Services e Controller #1

Open giordanolima opened 8 years ago

giordanolima commented 8 years ago

Boas práticas Models, Repositories, Services e Controller

Sugiro que seja criado um material explicando como seria num mundo ideal a Relação entre Models, Repositories, Services e Controller... Qual a responsabilidade de cada um deles, onde cada um é "chamado" etc... Coisa simples... Tipo um crud completo de notícias ou algum outro exemplo que encaixe bem essas ferramentas.

giordanolima commented 8 years ago

Só pra complementar com um comentário pessoal.. Eu já estou habituado com Repositories, porém ainda não vislumbro bem onde entram os Sevices na jogada....

deyvisonrocha commented 8 years ago

+1

Estou por aí @giordanolima , não sei onde os Services entram quando temos um Repository Pattern. Apesar de que o artigo do @vinicius73 fez no Repository Pattern não precisa ser chato, principalmente com Laravel explicou muita coisa.

diegofersan commented 8 years ago

+1

Estou passando por essa mesma situação. Vasculhei alguns projetos, mas ainda não compreendi como aplicar os services.

vs0uz4 commented 8 years ago

@deyvisonrocha pelo que eu entendi, creio que quando os métodos fogem do conceito de apenas conversar com o banco de dados, nesse caso caímos para um service. Por exemplo cadastrar um usuário, enviar um e-mail para o usuário, os métodos para o CRUD seriam no Repository já me todos para envio de e-mail e outros comportamentos ficariam em um Service Layer. Mas confesso que não tive confirmação ainda se meu pensamento está correto ou não.

deyvisonrocha commented 8 years ago

@julihermes

fxcosta commented 8 years ago

É quase isso, @vs0uz4. A ideia do Repository é somente manter acesso a base. Quando você passa a ter muita regra de negócio dentro do Repository isso deixa de ser um simples acesso comum e passa a ser uma regra do negócio em si. A ideia é que você mantenha as regras de negócio nas camadas de Services porque isso te deixaria livre pra trocar Models, Repositories, patterns (Active Record ou Data Mapper) sem afetar a regra em si.

Fica mais simples de testar e de manter desacoplado. Se houver um post sobre isso será ótimo, mas, pelo menos a forma com a qual trabalho, o request funciona dessa forma:

view -> controller (recebe o request) -> Services (trabalha o objeto a ser persistido) -> Repositories (Persiste o objeto em si). No caso do Laravel Repository está acoplado com Model, ou seja, basicamente o uso além de manter o mapeando com as colunas da base.

Posso ter falado besteira, então, se estiver errado me corrijam por favor :-)

diegofersan commented 8 years ago

@vs0uz4, esse foi também meu entendimento. Ou até no caso dos dois métodos conversarem com o banco de dados, a criação do usuário seguida da criação do profile, por exemplo. Mas minha dúvida foi, onde entra isso na estrutura de pastas? Como chamar o service no Controller da forma correta?

fxcosta commented 8 years ago

@diegofersan Eu injeto a Service que vou precisar trabalhar e a uso normalmente. Quando recebo um request do tipo POST eu simplesmente passo algo como: $this->userService->save($request);

Trabalhando sempre com interfaces, então, eu declaro uma dependência de uma interface comum a UserService, e faço essa classe ser instanciada toda vez que encontrar algo como:

public function store(UserServiceInterface $service);

diegofersan commented 8 years ago

@fxcosta já ajudou muito a clarear as ideias. Definitivamente eu preciso estudar mais sobre o uso de interfaces.

julihermes commented 8 years ago

Minha idéia de Repository Pattern é muito parecida com a do @vs0uz4, usando o exemplo dele, ações que persistem no banco as defino em uma camada de Repositório (App\Repository), outras funções como, envio de email, upload ou qualquer coisa que seja de auxílio a minhas regras de negócio eu defino em uma camanda de Serviço (App\Service).

Em relação a injeção de dependência para esse cenário, basicamente, no Controller é injetado o Repository, e no Repository, caso precise, é injetado os Services (Claro que existem exceções).

Essa é a minha idéia, com relação ao que eu entendi, não sei se é a mais correta ou a mais aceita, tenho a mesma dúvida que levou o @giordanolima a criar essa pergunta.

vinicius73 commented 8 years ago

Não existe um fluxo "correto" Você pode usar services q chamam repos e repos q chamam services. Eu gosto de usar repos pata chamarem services, assim eu diminuo q qtd de classes q meu controller deve manipular. Porém a situações onde isso não é legal, e o repo passa a "chamar" vários services. Sempre é importante "pesar" o impacto de cada uso. No final a solução mais fleixivel é usar services para todas as ações de crud, e até para querys. Você terá q manipular mais classes no controller, porém a manutenção ficará extremamente produtiva.

cbcaio commented 8 years ago

Acho o tópico muito válido! Só acho que por ser um assunto relativamente "polêmico" onde, assim como muitos de nossos colegas dizem, não existe "certo", quem se disponibilizar em fazer o conteúdo tem q ficar atento para não ser tendencioso e "impor" (mesmo que de maneira inconsciente) um workflow que talvez seja mais pessoal doque qualquer outra coisa.

emtudo commented 8 years ago

@giordanolima talvez ajude a entender onde o service entra: http://youtu.be/ya57z4VRm_o e o código fonte: https://github.com/resultsystems/storehouse-product

feeh27 commented 5 years ago

Tem um tutorial de "Design Pattern: Service Layer com Laravel 5", está em inglês, mas utilizando o tradutor da para entender. Segue link: https://m.dotdev.co/design-pattern-service-layer-with-laravel-5-740ff0a7b65f

arturcesarmelo commented 4 years ago

Sobre a estrutura de dados trafegada entre o repository e o service. Li em um blog no medium que feriria, na opnião do autor, a ideia do padrão se o repository devolver um Eloquent Model. Deveria ser um Objeto PHP padrão ou uma outra estrutura tal qual collections ou array. Algo pra corroborar essa teoria?

guilherme90 commented 4 years ago

@arturcesarmelo bom, pelo que eu entendo na teoria o Repository deve retornar um objeto puro PHP e não uma Collection Eloquent por exemplo. Mas se você tá usando Laravel, obviamente vai usar Eloquent e ele te oferece tudo que precisa.

Partindo deste principio, por que deveria retornar um objeto puro e não um Eloquent se a sua intenção de trocar de ORM é praticamente nula? Então digamos que isso na prática nem sempre se aplica.

Mas se você possui um sistema bem grande, e quer usar Doctrine em algumas situações, fica fácil de fazer isso quando se está trabalhando pra uma Interface. Já que neste caso você teria uma Interface Doctrine e não uma Eloquent.

Um exemplo prático seria se seu projeto fosse usar NoSQL e iria usar Doctrine pra MongoDB. Bastaria configurar o ORM e ter as interfaces pra ele e pronto. Funciona 100% e fica bom.

Vamos resumir, na minha opinião nem tudo em teoria está na prática. Isso vai 100% do caso de uso. Eu uso Repository e retorno uma Collection por que é o que funciona bem demais pro meu caso. E uso o Services pra regras de negócio, por que também funciona perfeitamente. Fica bem separado, é fácil de testar, menos acoplado e cada um no seu quadrado.

Abraços.

judsonmb commented 4 years ago

Olá, estou iniciando nesse pattern num software onde só há consulta de dados (um painel) e gostaria de saber se estou fazendo da forma certa. Estou usando métodos estáticos e chamando a classe do repository para devolver os dados. Como é só consulta, não estou usando services, apenas um utils para transformar uns dados. Por favor, critiquem para q eu possa melhorar

estou chamando dessa forma:

Controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\SolicitationRepository;
use App\Repositories\PersonRepository;
use Illuminate\Support\Facades\Http;
use App\Utils\DataBuilder;

class SolicitationController extends Controller
{
    public function index(Request $request)
    {       
        $data = DataBuilder::prepareData($request);

        $customers = PersonRepository::getCustomers();    

        $solicitations_list = SolicitationRepository::getSolicitationRankingPeriodList($data);

        return view('solicitations', compact('data','customers', 'name', 'solicitations_list'));
    }
}

Repository:

<?php 

namespace App\Repositories;

use App\Solicitation;
use DB;

class SolicitationRepository
{
    public static function getSolicitationsCount($data){

        $sc = Solicitation:: [ QUERY, NÃO PUS PARA NÃO FICAR GRANDE ]

        return $sc;
    }

    public static function getSolicitationRankingPeriodList($data){

        $sr = Solicitation:: [ QUERY. NAO PUS PRA N FICAR GRANDE ]

        return $sr;
    }

}
guilherme90 commented 4 years ago

@judsonmb eu não usaria métodos estáticos nesse cenário, por causa de segregação de interface para seus repositórios.

Eu uso estático pra classe de Exception, Value Object (na maioria dos casos) ou algum cenário específico.

Entenda que no seu caso, seu controlador conhece a implementação. O ideal é que ele não saiba da implementação e que depende de uma abstração.

Quanto ao não uso do método estático, é que você sempre terá que fazer referência estática de métodos e atributos dentro da classe (o $this não é permitido em estático). Além disso, um objeto estático não há instância do objeto e é obrigado a chamar diretamente um método.

E neste seu cenário, existe acoplamento o que não é bom. O ideal é você usar DI, assim você fica livre do acoplamento e garante que aquele objeto depende de uma interface para que algum comportamento de fato seja feito.

E pra finalizar: testar fica mais complicado. Claro que, esses são meus pontos de vista. Existe os pós de usar estático (Façade é um bom exemplo...), além dos que já citei acima.

Abraços.

thallesrangel commented 3 years ago

Uma boa opção é observar este código: https://github.com/jsafe00/laravel-service-repository

Basicamente o fluxo se dá por, Controller -> Service -> Repository - > Model

Controller filtra solicitação (request), pode ser via form request mesmo, passa isso para o service, observer no controller PostController o $this->post, via injeção de dependência.. e passa por todo fluxo.