Qual é a precisão da “lógica de negócios em um serviço, não em um modelo”?

397

Situação

No início desta noite, respondi a uma pergunta no StackOverflow.

A questão:

A edição de um objeto existente deve ser feita na camada de repositório ou em serviço?

Por exemplo, se eu tiver um usuário com dívida. Eu quero mudar a dívida dele. Devo fazê-lo no UserRepository ou no serviço, por exemplo, BuyingService, obtendo um objeto, editando-o e salvando-o?

Minha resposta:

Você deve deixar a responsabilidade de alterar um objeto para o mesmo objeto e usar o repositório para recuperar esse objeto.

Exemplo de situação:

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

Um comentário que recebi:

A lógica de negócios deve realmente estar em um serviço. Não em um modelo.

O que a internet diz?

Então, isso me fez procurar, porque eu nunca (conscientemente) realmente usei uma camada de serviço. Comecei a ler sobre o padrão Camada de serviço e o padrão Unidade de trabalho, mas até agora não posso dizer que estou convencido de que uma camada de serviço deve ser usada.

Tomemos, por exemplo, este artigo de Martin Fowler sobre o antipadrão de um modelo de domínio anêmico:

Existem objetos, muitos nomeados após os substantivos no espaço do domínio, e esses objetos estão conectados aos ricos relacionamentos e estrutura que os verdadeiros modelos de domínio possuem. O problema ocorre quando você olha para o comportamento e percebe que quase não há comportamento nesses objetos, tornando-os pouco mais do que sacos de caçadores e caçadores. Na verdade, esses modelos geralmente vêm com regras de design que dizem que você não deve colocar nenhuma lógica de domínio nos objetos de domínio. Em vez disso, há um conjunto de objetos de serviço que capturam toda a lógica do domínio. Esses serviços ficam no topo do modelo de domínio e usam o modelo de domínio para dados.

(...) A lógica que deve estar em um objeto de domínio é lógica de domínio - validações, cálculos, regras de negócios - como você quiser chamar.

Para mim, isso parecia exatamente o que era a situação: eu advoguei a manipulação dos dados de um objeto introduzindo métodos dentro dessa classe que fazem exatamente isso. No entanto, percebo que isso deve ser um dado de qualquer maneira, e provavelmente tem mais a ver com a forma como esses métodos são chamados (usando um repositório).

Eu também tinha a sensação de que, naquele artigo (veja abaixo), uma Camada de Serviço é mais considerada uma fachada que delega trabalho para o modelo subjacente do que uma camada que exige muito trabalho.

Camada de aplicativo [seu nome para Camada de serviço]: define os trabalhos que o software deve executar e direciona os objetos de domínio expressivos para solucionar problemas. As tarefas pelas quais essa camada é responsável são significativas para os negócios ou necessárias para a interação com as camadas de aplicativos de outros sistemas. Essa camada é mantida fina. Ele não contém regras ou conhecimentos de negócios, mas apenas coordena tarefas e delegados trabalham para colaborações de objetos de domínio na próxima camada abaixo. Não possui um estado que reflita a situação do negócio, mas pode ter um estado que reflete o progresso de uma tarefa para o usuário ou o programa.

O que é reforçado aqui :

Interfaces de serviço. Os serviços expõem uma interface de serviço à qual todas as mensagens de entrada são enviadas. Você pode pensar em uma interface de serviço como uma fachada que expõe a lógica de negócios implementada no aplicativo (normalmente, lógica na camada de negócios) a possíveis consumidores.

E aqui :

A camada de serviço deve ser desprovida de qualquer aplicativo ou lógica de negócios e deve se concentrar principalmente em algumas preocupações. Ele deve agrupar as chamadas da camada de negócios, traduzir seu domínio em um idioma comum que seus clientes possam entender e manipular o meio de comunicação entre o servidor e o cliente solicitante.

Este é um contraste sério com outros recursos que falam sobre a camada de serviço:

A camada de serviço deve consistir em classes com métodos que são unidades de trabalho com ações que pertencem à mesma transação.

Ou a segunda resposta a uma pergunta que eu já vinculei:

Em algum momento, seu aplicativo desejará alguma lógica de negócios. Além disso, convém validar a entrada para certificar-se de que não haja algo ruim ou não-desempenho sendo solicitado. Essa lógica pertence à sua camada de serviço.

"Solução"?

Seguindo as diretrizes nesta resposta , criei a seguinte abordagem que usa uma Camada de Serviço:

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

Conclusão

Tudo junto não mudou muito aqui: o código do controlador foi movido para a camada de serviço (o que é uma coisa boa, então há uma vantagem nessa abordagem). No entanto, isso não parece ter nada a ver com a minha resposta original.

Percebo que os padrões de design são diretrizes, não regras definidas em pedra a serem implementadas sempre que possível. Ainda não encontrei uma explicação definitiva da camada de serviço e como ela deve ser considerada.

  • É um meio de simplesmente extrair a lógica do controlador e colocá-la dentro de um serviço?

  • Ele deveria formar um contrato entre o controlador e o domínio?

  • Deve haver uma camada entre o domínio e a camada de serviço?

E, por último mas não menos importante: após o comentário original

A lógica de negócios deve realmente estar em um serviço. Não em um modelo.

  • Isso está correto?

    • Como introduziria minha lógica de negócios em um serviço em vez do modelo?
Jeroen Vannevel
fonte
6
Trato a camada de serviço como o local onde colocar a parte inevitável do script de transação que atua sobre as raízes agregadas. Se minha camada de serviço se tornar muito complexa, isso indica que eu me movo na direção do Modelo Anêmico e que meu modelo de domínio precisa de atenção e revisão. Eu também tento colocar a lógica no SL, que não será duplicada por sua natureza.
Pavel Voronin
Eu pensei que os serviços faziam parte da camada Modelo. Estou errado em pensar isso?
Florian Margaine
Eu uso uma regra de ouro: não dependa do que você não precisa; caso contrário, eu posso ser (geralmente negativamente) impactado por mudanças na parte de que não preciso. Como conseqüência, acabo com muitas funções claramente definidas. Meus objetos de dados contêm comportamento desde que todos os clientes o usem. Caso contrário, movo o comportamento para classes que implementam a função necessária.
beluchin
1
A lógica de controle de fluxo do aplicativo pertence a um controlador. A lógica de acesso a dados pertence a um repositório. A lógica de validação pertence a uma camada de serviço. Uma camada de serviço é uma camada adicional em um aplicativo ASP.NET MVC que medeia a comunicação entre um controlador e uma camada de repositório. A camada de serviço contém lógica de validação de negócios. repositório. asp.net/mvc/overview/older-versions-1/models-data/...
Kbdavis07
3
IMHO, o modelo de domínio correto no estilo OOP deve, de fato, evitar "serviços de negócios" e manter a lógica de negócios no modelo. Mas a prática mostra que é tão tentador criar uma enorme camada de negócios com métodos com nomes distintos e também colocar uma transação (ou unidade de trabalho) em torno de todo o método. É muito mais fácil processar várias classes de modelo em um método de serviço de negócios do que planejar relações entre essas classes de modelo, porque você teria algumas perguntas difíceis para responder: onde está a raiz agregada? Onde devo iniciar / confirmar uma transação de banco de dados? Etc.
JustAMartin

Respostas:

368

Para definir quais são as responsabilidades de um serviço , você precisa primeiro definir o que é um serviço .

Serviço não é um termo canônico ou genérico de software. De fato, o sufixo Serviceno nome de uma classe é muito parecido com o muito criticado gerente : ele não diz quase nada sobre o que o objeto realmente faz .

Na realidade, o que um serviço deve fazer é altamente específico da arquitetura:

  1. Em uma arquitetura em camadas tradicional, serviço é literalmente sinônimo de camada de lógica de negócios . É a camada entre interface do usuário e dados. Portanto, todas as regras de negócios entram em serviços. A camada de dados deve entender apenas operações CRUD básicas e a camada da interface do usuário deve lidar apenas com o mapeamento dos DTOs de apresentação para e a partir dos objetos de negócios.

  2. Em uma arquitetura distribuída no estilo RPC (SOAP, UDDI, BPEL etc.), o serviço é a versão lógica de um terminal físico . É essencialmente uma coleção de operações que o mantenedor deseja fornecer como uma API pública. Vários guias de práticas recomendadas explicam que uma operação de serviço deve ser de fato uma operação em nível de negócios e não CRUD, e eu tendem a concordar.

    No entanto, como o roteamento de tudo por meio de um serviço remoto real pode prejudicar seriamente o desempenho, normalmente é melhor não ter esses serviços realmente implementando a lógica de negócios; em vez disso, eles devem agrupar um conjunto "interno" de objetos de negócios. Um único serviço pode envolver um ou vários objetos de negócios.

  3. Em uma arquitetura MVP / MVC / MVVM / MV *, os serviços não existem. Ou, se o fizerem, o termo será usado para se referir a qualquer objeto genérico que possa ser injetado em um controlador ou modelo de exibição. A lógica de negócios está no seu modelo . Se você deseja criar "objetos de serviço" para orquestrar operações complicadas, isso é visto como um detalhe de implementação. Infelizmente, muitas pessoas implementam o MVC assim, mas é considerado um antipadrão ( Anemic Domain Model ) porque o modelo em si não faz nada, são apenas um monte de propriedades para a interface do usuário.

    Algumas pessoas pensam erroneamente que usar um método de controlador de 100 linhas e colocar tudo em um serviço de alguma forma contribui para uma arquitetura melhor. Realmente não; tudo o que faz é adicionar outra camada de indireção provavelmente desnecessária. Na prática , o controlador ainda está fazendo o trabalho, apenas através de um objeto "auxiliar" mal nomeado. Eu recomendo a apresentação Wicked Domain Models de Jimmy Bogard para um exemplo claro de como transformar um modelo de domínio anêmico em um útil. Envolve um exame cuidadoso dos modelos que você está expondo e quais operações são realmente válidas em um contexto de negócios .

    Por exemplo, se o seu banco de dados contiver Pedidos e você tiver uma coluna para Valor total, seu aplicativo provavelmente não deverá alterar esse campo para um valor arbitrário, porque (a) é histórico e (b) deve ser determinado pelo que está em ordem e talvez por outros dados / regras sensíveis ao tempo. A criação de um serviço para gerenciar pedidos não resolve necessariamente esse problema, porque o código do usuário ainda pode pegar o objeto real do pedido e alterar a quantidade nele. Em vez disso, o próprio pedido deve ser responsável por garantir que só possa ser alterado de maneira segura e consistente.

  4. No DDD, os serviços destinam-se especificamente à situação em que você possui uma operação que não pertence adequadamente a nenhuma raiz agregada . Você precisa ter cuidado aqui, porque muitas vezes a necessidade de um serviço pode implicar que você não usou as raízes corretas. Mas, supondo que sim, um serviço é usado para coordenar operações em várias raízes ou, às vezes, para lidar com preocupações que não envolvem o modelo de domínio (como, talvez, gravar informações em um banco de dados BI / OLAP).

    Um aspecto notável do serviço DDD é que ele pode usar scripts de transação . Ao trabalhar em aplicativos grandes, é provável que você acabe enfrentando instâncias em que é muito mais fácil realizar algo com um procedimento T-SQL ou PL / SQL do que se preocupar com o modelo de domínio. Isso está bom e pertence a um serviço.

    Este é um desvio radical da definição de serviços de arquitetura em camadas. Uma camada de serviço encapsula objetos de domínio; um serviço DDD encapsula o que não está nos objetos de domínio e não faz sentido.

  5. Em uma arquitetura orientada a serviços , um serviço é considerado a autoridade técnica para um recurso de negócios. Isso significa que ele é o proprietário exclusivo de um determinado subconjunto dos dados corporativos e nada mais pode tocar esses dados - nem mesmo lê- los.

    Por necessidade, os serviços são na verdade uma proposição de ponta a ponta em uma SOA. Ou seja, um serviço não é tanto um componente específico como uma pilha inteira , e seu aplicativo inteiro (ou toda a empresa) é um conjunto desses serviços que funcionam lado a lado sem interseção, exceto nas camadas de mensagens e UI. Cada serviço possui seus próprios dados, suas próprias regras de negócios e sua própria interface do usuário. Eles não precisam se orquestrar porque devem estar alinhados aos negócios - e, como a própria empresa, cada serviço tem seu próprio conjunto de responsabilidades e opera mais ou menos independentemente dos outros.

    Portanto, pela definição de SOA, toda parte da lógica de negócios em qualquer lugar está contida no serviço, mas também o sistema inteiro . Os serviços em uma SOA podem ter componentes e pontos de extremidade , mas é bastante perigoso chamar qualquer parte de código de serviço porque entra em conflito com o que o "S" original deve significar.

    Como a SOA geralmente é bastante interessada em mensagens, as operações que você pode ter empacotado em um serviço antes geralmente são encapsuladas em manipuladores , mas a multiplicidade é diferente. Cada manipulador lida com um tipo de mensagem, uma operação. É uma interpretação estrita do Princípio da Responsabilidade Única , mas oferece grande facilidade de manutenção, pois todas as operações possíveis estão em sua própria classe. Portanto, você realmente não precisa de lógica de negócios centralizada, porque os comandos representam operações de negócios e não técnicas.

Por fim, em qualquer arquitetura que você escolher, haverá algum componente ou camada que possui a maior parte da lógica de negócios. Afinal, se a lógica de negócios estiver espalhada por todo o lugar, você terá apenas um código de espaguete. Mas se você chama esse componente de serviço ou não , e como ele é projetado em termos de número ou tamanho de operações, depende de seus objetivos arquitetônicos.

Não há resposta certa ou errada, apenas o que se aplica à sua situação.

Aaronaught
fonte
12
Obrigado pela resposta muito elaborada, você esclareceu tudo o que consigo pensar. Embora as outras respostas também sejam de boa a excelente qualidade, acredito que essa resposta supere todas elas, portanto, aceitarei essa. Vou adicioná-lo aqui para outras respostas: qualidade e informações excelentes, mas, infelizmente, só poderei lhe dar um voto positivo.
Jeroen Vannevel
2
Não concordo com o fato de que, em uma arquitetura tradicional em camadas, serviço é sinônimo da camada de lógica de negócios.
CodeART 14/11
1
@ Codeode: Está em uma arquitetura de três camadas. Eu tenho visto arquiteturas 4-tier onde há uma "camada de aplicação" entre as camadas de apresentação e de negócios, o que às vezes é também chamado de "serviço" camada, mas honestamente, os únicos lugares que eu já vi isto implementado com sucesso são enormes alastrando produtos infinitamente configuráveis ​​para executar todo o seu negócio por você, como SAP ou Oracle, e não achei que valesse a pena mencionar aqui. Posso adicionar um esclarecimento, se quiser.
Aaronaught
1
Mas se pegarmos mais de 100 controladores de linha (por exemplo, o controlador aceita mensagem - desserialize o objeto JSON, faça a validação, aplique regras de negócios, salve no db, retorne o objeto de resultado) e mova alguma lógica para um dos chamados métodos de serviço '' Isso nos ajuda a testar cada unidade separadamente sem dor?
Artjom
2
@Aaronaught Eu queria esclarecer uma coisa se tivermos objetos de domínio mapeados para db via ORM e não houver lógica de negócios neles, é este modelo de domínio anêmico ou não?
Artjom
40

Quanto ao seu título , não acho que a pergunta faça sentido. O modelo MVC consiste em dados e lógica de negócios. Dizer que a lógica deve estar no Serviço e não o Modelo é como dizer: "O passageiro deve sentar no banco, não no carro".

Então, novamente, o termo "Modelo" é um termo sobrecarregado. Talvez você não quis dizer o modelo MVC, mas o modelo no sentido de objeto de transferência de dados (DTO). AKA uma Entidade. É disso que Martin Fowler está falando.

Na minha opinião, Martin Fowler está falando das coisas em um mundo ideal. No mundo real do Hibernate e JPA (em Java), os DTOs são uma abstração super vazada. Eu adoraria colocar minha lógica de negócios em minha entidade. Isso tornaria as coisas mais limpas. O problema é que essas entidades podem existir em um estado gerenciado / em cache que é muito difícil de entender e impede constantemente seus esforços. Para resumir minha opinião: Martin Fowler está recomendando o caminho certo, mas os ORMs impedem você de fazê-lo.

Eu acho que Bob Martin tem uma sugestão mais realista e ele dá neste vídeo que não é gratuito . Ele fala sobre manter seus DTOs livres de lógica. Eles simplesmente retêm os dados e os transferem para outra camada que é muito mais orientada a objetos e não usa os DTOs diretamente. Isso evita que a abstração com vazamento te morda. A camada com os DTOs e os próprios DTOs não são OO. Mas uma vez que você sai dessa camada, você fica tão OO quanto Martin Fowler defende.

O benefício dessa separação é que ela abstrai a camada de persistência. Você poderia mudar do JPA para o JDBC (ou vice-versa) e nenhuma lógica de negócios precisaria ser alterada. Depende apenas dos DTOs, não importa como esses DTOs serão preenchidos.

Para alterar um pouco os tópicos, é necessário considerar o fato de que os bancos de dados SQL não são orientados a objetos. Mas os ORMs geralmente têm uma entidade - que é um objeto - por tabela. Então, desde o início, você já perdeu uma batalha. Na minha experiência, você nunca pode representar a Entidade da maneira exata que deseja de uma maneira orientada a objetos.

Quanto a " um serviço", Bob Martin seria contra ter uma classe nomeada FooBarService. Isso não é orientado a objetos. O que um serviço faz? Qualquer coisa relacionada a FooBars. Também pode ser rotulado FooBarUtils. Eu acho que ele defenderia uma camada de serviço (um nome melhor seria a camada da lógica de negócios), mas todas as classes nessa camada teriam um nome significativo.

Daniel Kaplan
fonte
2
Concorde com seu ponto de vista sobre ORMs; eles propagam uma mentira de que você mapeia sua entidade diretamente para o banco de dados com eles, quando na realidade uma entidade pode ser armazenada em várias tabelas.
218 Andy Andy
@Daniel Kaplan, você sabe qual é o link atualizado para o vídeo de Bob Martin?
Brian Morearty
25

Estou trabalhando no projeto greenfield agora e tivemos que tomar poucas decisões arquitetônicas ontem. Curiosamente, tive que revisitar alguns capítulos de 'Patterns of Enterprise Application Architecture'.

Isto é o que nós conseguimos:

  • Camada de dados. Consultas e atualizações de banco de dados. A camada é exposta através de repositórios injetáveis.
  • Camada de domínio. É aqui que vive a lógica do negócio. Essa camada faz uso de repositórios injetáveis ​​e é responsável pela maior parte da lógica de negócios. Esse é o núcleo do aplicativo que testaremos completamente.
  • Camada de serviço. Essa camada se comunica com a camada de domínio e atende às solicitações do cliente. No nosso caso, a camada de serviço é bastante simples - retransmite solicitações para a camada de domínio, lida com segurança e poucas outras preocupações transversais. Isso não é muito diferente de um controlador no aplicativo MVC - os controladores são pequenos e simples.
  • Camada do cliente. Fala com a camada de serviço através do SOAP.

Terminamos com o seguinte:

Cliente -> Serviço -> Domínio -> Dados

Podemos substituir a camada de cliente, serviço ou dados por uma quantidade razoável de trabalho. Se sua lógica de domínio residia no serviço e você decidiu substituir ou mesmo remover sua camada de serviço, seria necessário mover toda a lógica de negócios para outro lugar. Tal exigência é rara, mas pode acontecer.

Tendo dito tudo isso, acho que isso é bem próximo do que Martin Fowler quis dizer com

Esses serviços ficam no topo do modelo de domínio e usam o modelo de domínio para dados.

O diagrama abaixo ilustra muito bem isso:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif

CodeART
fonte
2
Você decidiu SOAP ontem? Isso é um requisito ou você simplesmente não tem uma ideia melhor?
JensG
1
O REST não atende aos nossos requisitos. SOAP ou REST, isso não faz nenhuma diferença para a resposta. Pelo meu entendimento, o serviço é uma porta de entrada para a lógica do domínio.
CodeART 14/11
Concordo absolutamente com o Gateway. SOAP é bloatware (padronizado), então tive que perguntar. E sim, também não há impacto na pergunta / resposta.
JensG
6
Faça um favor a si mesmo e mate sua camada de serviço. Sua interface do usuário deve usar seu domínio diretamente. Já vi isso antes e seu domínio invariavelmente se torna um monte de dtos anêmicos, não de modelos ricos.
Andy
Essas lâminas cobrir a explicação sobre camada de serviço e este gráfico acima consideravelmente puro: slideshare.net/ShwetaGhate2/...
Marc Juchli
9

Essa é uma daquelas coisas que realmente depende do caso de uso. O ponto geral de uma camada de serviço é consolidar a lógica de negócios juntos. Isso significa que vários controladores podem chamar o mesmo UserService.MakeHimPay () sem realmente se preocupar com a forma como o pagamento é feito. O que acontece no serviço pode ser tão simples quanto modificar uma propriedade de objeto ou fazer lógica complexa ao lidar com outros serviços (por exemplo, chamar serviços de terceiros, chamar lógica de validação ou simplesmente salvar algo no banco de dados. )

Isso não significa que você precise remover TODA a lógica dos objetos de domínio. Às vezes, faz mais sentido que um método no objeto de domínio faça alguns cálculos por si mesmo. No seu exemplo final, o serviço é uma camada redundante sobre o objeto de repositório / domínio. Ele fornece um bom buffer contra alterações de requisitos, mas não é realmente necessário. Se você acha que precisa de um serviço, tente fazer a lógica simples "modificar propriedade X no objeto Y" em vez do objeto de domínio. A lógica nas classes de domínio tende a cair no "calcular este valor a partir dos campos" em vez de expor todos os campos por meio de getters / setters.

firelore
fonte
2
Sua posição a favor de uma camada de serviço com lógica de negócios faz muito sentido, mas isso ainda deixa algumas questões. No meu post, citei várias fontes respeitáveis ​​que falam sobre a camada de serviço como uma fachada vazia de qualquer lógica de negócios. Isso está em contraste direto com a sua resposta, você poderia talvez esclarecer essa diferença?
Jeroen Vannevel 11/11
5
Acho que realmente depende do TIPO da lógica de negócios e de outros fatores, como o idioma que está sendo usado. Alguma lógica de negócios não se encaixa muito bem nos objetos de domínio. Um exemplo é filtrar / classificar os resultados depois de retirá-los do banco de dados. É lógica de negócios, mas não faz sentido no objeto de domínio. Acho que os serviços são mais utilizados para lógica simples ou transformar os resultados e a lógica no domínio é mais útil quando se trata de salvar dados ou calcular dados do objeto.
Firelore #
8

A maneira mais fácil de ilustrar por que os programadores evitam colocar a lógica de domínio nos objetos de domínio é que eles geralmente enfrentam uma situação de "onde eu coloco a lógica de validação?" Tome este objeto de domínio, por exemplo:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

Portanto, temos alguma lógica básica de validação no setter (não pode ser negativa). O problema é que você realmente não pode reutilizar essa lógica. Em algum lugar, há uma tela ou um ViewModel ou um Controller que precisa validar antes de efetivamente confirmar a alteração no objeto de domínio, porque precisa informar o usuário antes ou quando clica no botão Salvar que não pode fazer isso, e por que . Testar uma exceção quando você chama o setter é um truque feio, porque você realmente deveria ter feito toda a validação antes mesmo de iniciar a transação.

É por isso que as pessoas movem a lógica de validação para algum tipo de serviço, como MyEntityValidator. Em seguida, a entidade e a lógica de chamada podem obter uma referência ao serviço de validação e reutilizá-lo.

Se você não fizer isso e ainda quiser reutilizar a lógica de validação, acabará colocando-a em métodos estáticos da classe de entidade:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

Isso tornaria seu modelo de domínio menos "anêmico" e manteria a lógica de validação próxima à propriedade, o que é ótimo, mas não acho que alguém realmente goste dos métodos estáticos.

Scott Whitlock
fonte
1
Então, qual é a melhor solução para validação na sua opinião?
Flashrunner 5/09/16
3
@Flashrunner - minha lógica de validação está definitivamente na camada de lógica de negócios, mas também é duplicada nas camadas de entidade e banco de dados em alguns casos. A camada de negócios lida bem com isso informando o usuário, etc., mas as outras camadas lançam erros / exceções e impedem que o programador (eu) cometa erros que corrompem os dados.
Scott Whitlock
6

Acho que a resposta é clara se você ler o artigo Anemic Domain Model de Martin Fowler .

A remoção da lógica de negócios, que é o domínio, do modelo de domínio está essencialmente quebrando o design orientado a objetos.

Vamos revisar o conceito orientado a objetos mais básico: um objeto encapsula dados e operações. Por exemplo, fechar uma conta é uma operação que um objeto de conta deve executar por si próprio; portanto, ter uma camada de serviço executando essa operação não é uma solução orientada a objetos. É processual, e é a isso que Martin Fowler se refere quando está falando sobre um modelo de domínio anêmico.

Se uma camada de serviço fecha a conta, em vez de fechar o objeto da conta, você não tem um objeto real da conta. O "objeto" da sua conta é apenas uma estrutura de dados. O que você acaba fazendo, como sugere Martin Fowler, é um monte de sacolas com caçadores e montadores.

Carlos A Merighe - Utah
fonte
1
Editado. Na verdade, achei esta explicação bastante útil e não acho que mereça votos negativos.
BadHorsie
1
Existe uma desvantagem amplamente supervisionada dos modelos ricos. E isso significa que os desenvolvedores estão trazendo algo ligeiramente relacionado ao modelo. O estado de aberto / fechado é um atributo da conta? E o dono? E o banco? Todos eles devem ser referenciados pela conta? Eu encerraria uma conta conversando com um banco ou através da conta diretamente? Com modelos anêmicos, essas conexões não são uma parte inerente dos modelos, mas são criadas ao trabalhar com esses modelos em outras classes (chame-os de serviços ou gerenciadores).
Hubert Grzeskowiak
4

Como você implementaria sua lógica de negócios na camada de serviço? Ao efetuar um pagamento de um usuário, você cria um pagamento, não apenas deduzindo um valor de uma propriedade.

Sua forma de pagamento precisa criar um registro de pagamento, adicionar à dívida do usuário e persistir tudo isso em seus repositórios. Fazer isso em um método de serviço é incrivelmente simples e você também pode agrupar toda a operação em uma transação. Fazer o mesmo em um modelo de domínio agregado é muito mais problemático.

Cochese
fonte
2

A versão tl; dr:
Minhas experiências e opiniões dizem que qualquer objeto que tenha lógica de negócios deve fazer parte do modelo de domínio. O modelo de dados provavelmente não deve ter lógica alguma. Os serviços provavelmente devem unir os dois e lidar com preocupações transversais (bancos de dados, registros etc.). No entanto, a resposta aceita é a mais prática.

A versão mais longa, que já foi mencionada por outras pessoas, é que há um equívoco na palavra "modelo". A postagem alterna entre o modelo de dados e o modelo de domínio como se fossem iguais, o que é um erro muito comum. Também pode haver um pequeno equívoco na palavra "serviço".

Em termos práticos, você não deve ter um serviço que faça alterações em qualquer objeto do domínio; a razão para isso é que seu serviço provavelmente terá algum método para todas as propriedades do seu objeto, a fim de alterar o valor dessa propriedade. Este é um problema porque, se você tiver uma interface para seu objeto (ou mesmo se não tiver), o serviço não estará mais seguindo o Princípio Aberto-Fechado; em vez disso, sempre que você adicionar mais dados ao seu modelo (independentemente do domínio versus dados), você precisará adicionar mais funções ao seu serviço. Existem algumas maneiras de contornar isso, mas esse é o motivo mais comum pelo qual os aplicativos "corporativos" falham, especialmente quando essas organizações pensam que "corporativo" significa "ter uma interface para todos os objetos do sistema". Você pode imaginar adicionar novos métodos a uma interface, depois, para duas ou três implementações diferentes (a no aplicativo, a implementação simulada e a de depuração, a na memória?), apenas para uma única propriedade no seu modelo? Parece uma péssima ideia para mim.

Há um problema mais longo aqui que não abordarei, mas o essencial é o seguinte: a programação orientada a objetos hardcore diz que ninguém fora do objeto relevante deve ser capaz de alterar o valor de uma propriedade dentro do objeto, nem mesmo " veja "o valor da propriedade dentro do objeto. Isso pode ser aliviado, tornando os dados somente leitura. Você ainda pode ter problemas como quando muitas pessoas usam os dados mesmo como somente leitura e é necessário alterar o tipo desses dados. É possível que todos os consumidores tenham que mudar para acomodar isso. É por isso que, quando você faz com que as APIs sejam consumidas por qualquer pessoa e todos, é recomendável que você não tenha propriedades / dados públicos ou mesmo protegidos; é exatamente a razão pela qual o OOP foi inventado.

Acho que a maioria das respostas aqui, além da marcada como aceita, está obscurecendo a questão. O item aceito é bom, mas ainda senti a necessidade de responder e concordar que o marcador 4 é o caminho a percorrer, em geral.

No DDD, os serviços destinam-se especificamente à situação em que você possui uma operação que não pertence adequadamente a nenhuma raiz agregada. Você precisa ter cuidado aqui, porque muitas vezes a necessidade de um serviço pode implicar que você não usou as raízes corretas. Mas, supondo que sim, um serviço é usado para coordenar operações em várias raízes ou, às vezes, para lidar com preocupações que não envolvem o modelo de domínio.

WolfgangSenff
fonte
1

A resposta é que depende do caso de uso. Mas na maioria dos cenários genéricos, eu aderia à lógica de negócios que fica na camada de serviço. O exemplo que você forneceu é realmente simples. No entanto, quando você começa a pensar em sistemas ou serviços dissociados e adicionando um comportamento transacional, você realmente deseja que isso aconteça como parte da camada de serviço.

As fontes que você citou para a camada de serviço sem qualquer lógica comercial introduz outra camada, que é a camada comercial. Em muitos cenários, a camada de serviço e a camada de negócios são compactadas em uma. Realmente depende de como você deseja projetar seu sistema. Você pode fazer o trabalho em três camadas e continuar decorando e adicionando ruído.

O que você pode idealmente fazer é modelar serviços que abrangem a lógica de negócios para trabalhar em modelos de domínio para manter o estado . Você deve tentar desacoplar os serviços o máximo possível.

ensolarado
fonte
0

No MVC, o modelo é definido como a lógica de negócios. Alegar que deveria estar em outro lugar está incorreto, a menos que ele não esteja usando o MVC. Eu vejo as camadas de serviço como semelhantes a um sistema de módulos. Ele permite que você agrupe um conjunto de funcionalidades relacionadas em um bom pacote. Os internos dessa camada de serviço teriam um modelo fazendo o mesmo trabalho que o seu.

O modelo consiste em dados de aplicativos, regras de negócios, lógica e funções. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

pedregoso
fonte
0

O conceito de camada de serviço pode ser visto da perspectiva do DDD. Aaronaught mencionou isso em sua resposta, eu apenas elaborei um pouco.

Abordagem comum é ter um controlador específico para um tipo de cliente. Digamos, pode ser um navegador da web, pode ser alguma outra aplicação, pode ser um teste funcional. Os formatos de solicitação e resposta podem variar. Então, eu uso o serviço de aplicativo como uma ferramenta para utilizar uma arquitetura hexagonal . Eu injeto classes de infraestrutura específicas para uma solicitação concreta. Por exemplo, é assim que meu controlador que atende solicitações de navegador da Web pode parecer:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

Se estou escrevendo um teste funcional, quero usar um cliente de pagamento falso e provavelmente não precisaria de uma resposta html. Portanto, meu controlador pode ficar assim:

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Portanto, o serviço de aplicativo é um ambiente que eu configurei para executar a lógica de negócios. É onde as classes de modelo são chamadas - independentemente de uma implementação de infraestrutura.

Então, respondendo suas perguntas sob essa perspectiva:

É um meio de simplesmente extrair a lógica do controlador e colocá-la dentro de um serviço?

Não.

Ele deveria formar um contrato entre o controlador e o domínio?

Bem, pode-se chamar assim.

Deve haver uma camada entre o domínio e a camada de serviço?

Não.


Existe uma abordagem radicalmente diferente que nega totalmente o uso de qualquer tipo de serviço. Por exemplo, David West, em seu livro Object Thinking, afirma que qualquer objeto deve ter todos os recursos necessários para realizar seu trabalho. Essa abordagem resulta, por exemplo, no descarte de qualquer ORM .

Zapadlo
fonte
-2

Para o registro.

SRP:

  1. Model = Data, aqui vai o setter e getters.
  2. Lógica / Serviços = aqui vão as decisões.
  3. Repositório / DAO = aqui permanentemente armazenamos ou recuperamos as informações.

Nesse caso, não há problema em executar as próximas etapas:

Se a dívida não exigir algum cálculo:

userObject.Debt = 9999;

No entanto, se requer algum cálculo, então:

userObject.Debt= UserService.CalculateDebt(userObject)

ou também

UserService.UpdateDebt(userObject)

Mas também, se o cálculo for feito na camada de persistência, esse procedimento de armazenamento

UserRepository.UpdateDebt(userObject)

Nesse caso, queremos recuperar o usuário do banco de dados e atualizar a dívida; devemos fazê-lo em várias etapas (na verdade, duas) e não é necessário envolvê-lo / encapsulá-lo na função de um serviço.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

E se precisar armazená-lo, podemos adicionar um terceiro passo

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

Sobre a solução proposta

a) Não devemos ter medo de deixar o desenvolvedor final escrever alguns em vez de encapsulá-lo em uma função.

b) E sobre a Interface, alguns desenvolvedores adoram a interface e eles são bons, mas em vários casos, eles não são de forma alguma necessários.

c) O objetivo de um serviço é criar um sem atributos, principalmente porque podemos usar funções compartilhadas / estáticas. Também é fácil fazer o teste de unidade.

Magallanes
fonte
como isso responde à pergunta: Quão precisa é a “lógica de negócios deve estar em um serviço, não em um modelo”?
mosquito
3
Que tipo de sentença é "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function.se não fosse para o meu cavalo que eu não teria gasto esse ano na faculdade ' "Eu só posso citar Lewis Black?'.
Malaquias