Se você é forçado a usar um modelo de domínio Anêmica, onde você coloca a sua lógica de negócio e campos calculados?

StackOverflow https://stackoverflow.com/questions/1933351

Pergunta

Nosso atual O/RM ferramenta não permite, realmente, rico em modelos de domínio, de modo que somos forçados a utilizar anêmicos (DTO) entidades em todos os lugares.Isso tem funcionado bem, mas continuo a lutar com onde colocar basic com base no objeto de lógica de negócios e campos calculados.

Atual camadas:

  • Apresentação
  • Serviço
  • Repositório
  • Dados/Entidade

O nosso repositório camada tem a maioria das básico obtenção/validar/salvar lógica, embora a camada de serviço é muito mais complexa validação e gravação (uma vez que as operações de salvamento também fazer o registo, verificação de permissões, etc).O problema é onde colocar o código como este:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

ou

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

Quaisquer pensamentos?

Foi útil?

Solução

Vamos voltar ao básico:

Serviços

Os serviços vêm em 3 sabores: Serviços de domínio, Serviços de Aplicativo, e Serviços de infraestrutura

  • Serviços de domínio : Encapsula a lógica de negócios que não se encaixa naturalmente em um objeto de domínio. No seu caso, tudo da sua lógica de negócios.
  • Serviços de Aplicativo : Usado por consumidores externos para conversar com seu sistema
  • Serviços de infraestrutura : Usado para abstrair preocupações técnicas (por exemplo, MSMQ, provedor de email, etc)

Repositório

É aqui que seu acesso de dados e verificações de consistência vai. Em puro DDD, seu Raízes agregadas seria responsável por verificar a consistência (antes de persistir qualquer objetivo). No seu caso, você usaria cheques do seu Serviços de domínio camada.


Solução proposta: Divida seus serviços existentes separados

Use um novo Serviços de domínio Camada para encapsular toda a lógica para seus DTOs e suas verificações de consistência também (usando Especificações, pode ser?).

Use o Serviço de Aplicativo para expor os métodos de busca necessária (FetchOpenOrdersWithLines), que encaminham os pedidos para o seu Repositório (e use genéricos, como Jeremy sugeriu). Você também pode considerar usar Especificações de consulta Para embrulhar suas perguntas.

De você Repositório, usar Especificações na tua Serviços de domínio Camada para verificar a consistência do objeto etc antes de persistir seus objetos.

Você pode encontrar informações de suporte no livro de Evans:

  • "Serviços e a camada de domínio isolada" (pág. 106)
  • "Especificações" (pág. 224)
  • "Especificações de consulta" (pág. 229)

Outras dicas

Estou tentado a responder Mu, mas eu gostaria de elaborar. Resumindo: Não deixe sua escolha do ORM ditar como você define seu modelo de domínio.

O objetivo do modelo de domínio é ser uma rica API orientada a objetos que modela o domínio. Seguir verdadeiro Design orientado a domínio, o modelo de domínio deve ser definido irrestrito pela tecnologia.

Em outras palavras, o O modelo de domínio vem primeiro, e todas as implementações específicas da tecnologia são posteriormente abordadas por Mappers Esse mapa entre o modelo de domínio e a tecnologia em questão. Isso geralmente inclui os dois lados: para a camada de acesso a dados, onde a escolha do ORM pode introduzir restrições e a camada da interface do usuário em que a tecnologia da interface do usuário impõe requisitos adicionais.

Se a implementação estiver extraordinariamente longe do modelo de domínio, falamos sobre um Camada anticorrupção.

No seu caso, o que você chama de modelo de domínio anêmico é na verdade a camada de acesso a dados. Seu melhor recurso seria definir Repositórios Esse modelo acessa suas entidades de maneira neutra em tecnologia.

Como exemplo, vejamos a entidade do seu pedido. Modelar uma ordem sem restrições pela tecnologia pode nos levar a algo assim:

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

Observe que este é um objeto CLR antigo e simples ( Poco ) e, portanto, não é restrito pela tecnologia. Agora, a questão é como você entra e sai do seu armazenamento de dados?

Isso deve ser feito por meio de um IDERRepository abstrato:

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

Agora você pode implementar o IorderRepository usando seu ORM de escolha. No entanto, alguns ORMs (como a estrutura da entidade da Microsoft) exigem que você derivem as classes de dados de determinadas classes base, para que isso não se encaixe com objetos de domínio como POCOS. Portanto, o mapeamento é necessário.

O importante a perceber é que você pode ter tigrado aulas de dados fortemente que semanticamente assemelhar -se a suas entidades de domínio. No entanto, esse é um detalhe puro da implementação, portanto, não fique confuso com isso. Uma classe de pedido que deriva de por exemplo EntityObject não é uma classe de domínio - É um detalhe de implementação; portanto, quando você implementa o IDERRepository, você precisa mapear o pedido Classe de dados para a ordem Classe Doman.

Este pode ser um trabalho tedioso, mas você pode usar Automapper fazer isso por você.

Veja como uma implementação do método SelectSingle pode parecer:

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

É exatamente para isso que é a camada de serviço - também vi aplicativos onde se chama BusinessLogic Cayer.

Essas são as rotinas que você deseja gastar a maior parte do tempo testando e, se elas estão em sua própria camada, zombando da camada do repositório deve ser direta.

A camada de repositório deve ser genicizada o máximo possível, portanto, não é um local apropriado para a lógica de negócios que seja individual para classes específicas.

A partir do que você diz pode ser que você está pensando muito rigidamente sobre o seu Serviço de Repositório e camadas.Parece que você não quer que sua camada de Apresentação tem uma dependência direta sobre a camada de Repositório e para conseguir isso, você está duplicando métodos de seus Repositórios (a sua passagem através de métodos) na camada de Serviço.

Eu ia pergunta isso.Você pode relaxar e permitir que tanto para ser usado dentro da sua camada de Apresentação e tornar a sua vida mais simples para começar.Talvez pergunte a si mesmo o que a sua realização pela ocultação de Repositórios assim.Você já está aprendendo a persistência e consulta a IMPLEMENTAÇÃO com eles.Isso é ótimo e o que eles são projetados para.Parece que você está tentando criar uma camada de serviço que esconde o fato de suas entidades são mantidos em todos os.Eu gostaria de perguntar por que?

Como para o cálculo do total de pedidos, etc.O Serviço de camada poderia ser o lugar natural.Um SalesOrderCalculator classe com LineTotal(LineItem lineItem) e Totaldopedido(Ordem) métodos estaria bem.Você também pode querer considerar a criação de um adequado Fábrica e.g.OrderServices.CreateOrderCalculator() para alternar a aplicação, se necessário (imposto sobre o pedido de desconto país tem regras específicas, por exemplo).Isso também poderia formar um único ponto de entrada para os serviços de Ordem e fazer encontrar as coisas mais fáceis através do IntelliSense.

Se tudo isso parece impossível pode ser que você precisa para pensar mais profundamente sobre o que o seu abstrações estão conseguindo, como eles se relacionam uns com os outros e com o Único Princípio De Responsabilidade.Um Repositório é uma infra-estrutura de abstração (escondendo-se COMO entidades são salvas e recuperadas).Serviços abstraem a implementação de ações de negócios ou de regras e permitir uma melhor estrutura para o controle de versão ou de desvio.Eles não são, geralmente em camadas na maneira de descrever.Se você tem complexo de regras de segurança em seus Serviços, os Repositórios podem ser o melhor em casa.Em um típico DDD modelo de estilo, Repositórios, Entidades, Objetos de Valor e Serviços seriam usados juntamente com o outro na mesma camada e como parte do mesmo modelo.As camadas acima (normalmente apresentação) seria, portanto, isolado por essas abstrações.Dentro do modelo de execução de um serviço pode usar a abstração do outro.Um refinamento adicional adiciona regras a que contém referências para o qual as entidades ou objetos de valor impor de um modo mais formal do ciclo de vida do contexto.Para obter mais informações sobre isso eu recomendo estudar o Eric Evans livro ou Domain Driven Design Rapidamente.

Se a sua tecnologia ORM lida apenas bem com o DTO, isso não significa que você precisa lançar objetos de entidade rica. Você ainda pode envolver seus objetos DTO com objetos de entidade:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

Eu encontrei o novo livro de Dino Esposito Microsoft® .NET: Aplicativos de arquitetura para a empresa ser um ótimo repositório de conhecimento para esses tipos de perguntas e questões.

A camada de serviço.

Se você deseja adicionar um pouco de comportamento às suas entidades, mas não pode modificar suas entidades, experimente os métodos de extensão. Eu só faria isso por cenários simples como no seu exemplo. Qualquer coisa mais complexa ou que coordena entre várias entidades e/ou serviços, camadas ou o que quer que esteja em um serviço de domínio, conforme sugerido.

Por exemplo (de seus exemplos):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

Se houver muito poucas dessas adições que você deseja, basta colocá -las em uma aula de "extensões de domínio", mas, de outra forma .

FYI: A única vez que fiz isso é quando eu tinha uma solução L2S e não queria mexer com os parciais. Eu também não tinha muitas extensões porque a solução era pequena. Gosto melhor da ideia de seguir uma camada completa de serviços de domínio.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top