Pergunta

Digamos que você tenha um aplicativo dividido em três camadas:GUI, lógica de negócios e acesso a dados.Na sua camada lógica de negócios você descreveu seus objetos de negócios:getters, setters, acessadores e assim por diante...Você entendeu a ideia.A interface para a camada de lógica de negócios garante o uso seguro da lógica de negócios, de modo que todos os métodos e acessadores que você chamar validarão a entrada.

Isso é ótimo quando você escreve o código da UI pela primeira vez, porque você tem uma interface bem definida na qual pode confiar.

Mas aí vem a parte complicada: quando você começa a escrever a camada de acesso a dados, a interface para a lógica de negócios não atende às suas necessidades.Você precisa ter mais acessadores e getters para definir campos que são/costumavam ser ocultos.Agora você é forçado a corroer a interface da sua lógica de negócios;agora é possível definir campos da camada UI, cuja camada UI não possui configuração de negócios.

Devido às mudanças necessárias para a camada de acesso a dados, a interface para a lógica de negócios foi desgastada a ponto de ser possível até mesmo configurar a lógica de negócios com dados inválidos.Assim, a interface não garante mais um uso seguro.

Espero ter explicado o problema com clareza suficiente.Como você evita a erosão da interface, mantém a ocultação e o encapsulamento de informações e ainda acomoda diferentes necessidades de interface entre diferentes camadas?

Foi útil?

Solução

Se entendi a pergunta corretamente, você criou um modelo de domínio e gostaria de escrever um mapeador objeto-relacional para mapear entre registros em seu banco de dados e seus objetos de domínio.No entanto, você está preocupado em poluir seu modelo de domínio com o código de 'encanamento' que seria necessário para ler e gravar nos campos do seu objeto.

Dando um passo atrás, você basicamente tem duas opções de onde colocar seu código de mapeamento de dados – dentro da própria classe de domínio ou em uma classe de mapeamento externa.A primeira opção costuma ser chamada de padrão Active Record e tem a vantagem de que cada objeto sabe como persistir e tem acesso suficiente à sua estrutura interna para permitir a execução do mapeamento sem a necessidade de expor campos não relacionados ao negócio.

Por exemplo

public class User
{
    private string name;
    private AccountStatus status;

    private User()
    {
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public AccountStatus Status
    {
        get { return status; }
    }

    public void Activate()
    {
        status = AccountStatus.Active;
    }

    public void Suspend()
    {
        status = AccountStatus.Suspended;
    }

    public static User GetById(int id)
    {
        User fetchedUser = new User();

        // Lots of database and error-checking code
        // omitted for clarity
        // ...

        fetchedUser.name = (string) reader["Name"];
        fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;

        return fetchedUser;
    }

    public static void Save(User user)
    {
        // Code to save User's internal structure to database
        // ...
    }
}

Neste exemplo, temos um objeto que representa um Usuário com um Nome e um AccountStatus.Não queremos permitir que o Status seja definido diretamente, talvez porque queiramos verificar se a mudança é uma transição de status válida, portanto não temos um configurador.Felizmente, o código de mapeamento nos métodos estáticos GetById e Save tem acesso total aos campos de nome e status do objeto.

A segunda opção é ter uma segunda classe responsável pelo mapeamento.Isto tem a vantagem de separar as diferentes preocupações de lógica de negócios e persistência, o que pode permitir que seu design seja mais testável e flexível.O desafio desse método é como expor os campos de nome e status à classe externa.Algumas opções são:1.Use a reflexão (que não tem escrúpulos em cavar profundamente as partes íntimas do seu objeto) 2.Forneça setters públicos com nomes especiais (por exemplo,Prefixo -os com a palavra 'privado') e espero que ninguém os use acidentalmente 3.Se a sua linguagem suportar isso, torne os setters internos, mas conceda acesso ao módulo do mapeador de dados.Por exemplo.use o InternalsVisibleToAttribute no .NET 2.0 em diante ou funções amigas em C++

Para obter mais informações, recomendo o livro clássico de Martin Fowler, 'Patterns of Enterprise Architecture'.

No entanto, como um aviso, antes de começar a escrever seus próprios mapeadores, recomendo fortemente usar uma ferramenta de mapeador relacional de objetos (ORM) de terceiros, como nHibernate ou Entity Framework da Microsoft.Trabalhei em quatro projetos diferentes onde, por vários motivos, escrevemos nosso próprio mapeador e é muito fácil perder muito tempo mantendo e estendendo o mapeador em vez de escrever código que forneça valor ao usuário final.Eu usei o nHibernate em um projeto até agora e, embora inicialmente tenha uma curva de aprendizado bastante acentuada, o investimento que você faz no início compensa consideravelmente.

Outras dicas

Este é um problema clássico: separar o modelo de domínio do modelo de banco de dados.Existem várias maneiras de atacá-lo, depende muito do tamanho do seu projeto na minha opinião.Você poderia usar o padrão de repositório como outros já disseram.Se você estiver usando .net ou java, você pode usar NHibernar ou Hibernar.

O que eu faço é usar Desenvolvimento orientado a testes então, escrevo minhas camadas de UI e modelo primeiro e a camada de dados é simulada, de modo que a UI e o modelo são construídos em torno de objetos específicos do domínio e, posteriormente, mapeio esses objetos para qualquer tecnologia que estou usando na camada de dados.É uma péssima ideia deixar o banco de dados determinar o design do seu aplicativo, escrever o aplicativo primeiro e pensar nos dados depois.

ps o título da pergunta é um pouco enganador

@Gelo ^^ Calor:

O que você quer dizer com que a camada de dados não deve estar ciente da camada lógica de negócios?Como você preencheria um objeto de negócios com dados?

A IU solicita um serviço ao ServiceClass na camada de negócios, ou seja, obtendo uma lista de objetos filtrados por um objeto com os dados de parâmetro necessários.
Em seguida, o ServiceClass cria uma instância de uma das classes de repositório na camada de dados e chama GetList (filtros ParameterType).
Em seguida, a camada de dados acessa o banco de dados, extrai os dados e os mapeia para o formato comum definido no assembly "domínio".
O BL não tem mais trabalho a fazer com esses dados, então ele os envia para a UI.

Em seguida, a IU deseja editar o Item X.Ele envia o item (ou objeto de negócios) para o serviço na camada de negócios.A camada de negócios valida o objeto e, se estiver OK, envia-o para a camada de dados para armazenamento.

A UI conhece o serviço na camada de negócios, que novamente conhece a camada de dados.

A UI é responsável por mapear a entrada de dados dos usuários de e para os objetos, e a camada de dados é responsável por mapear os dados no banco de dados de e para os objetos.A camada Business permanece puramente comercial.:)

Poderia ser uma solução, pois não desgastaria a interface.Eu acho que você poderia ter uma aula como esta:

public class BusinessObjectRecord : BusinessObject
{
}

Eu sempre crio uma montagem separada que contém:

  • Muitas interfaces pequenas (pense em ICreateRepository, IReadRepository, IReadListRepsitory..a lista continua e a maioria deles depende fortemente de genéricos)
  • Muitas interfaces concretas, como um IPersonRepository, que herda de IReadRepository, você entendeu.
    Qualquer coisa que você não consiga descrever apenas com as interfaces menores, você coloca na interface concreta.
    Contanto que você use o IPersonRepository para declarar seu objeto, você terá uma interface limpa e consistente para trabalhar.Mas o melhor é que você também pode criar uma aula que leve f.x.um ICreateRepository em seu construtor, então o código acabará sendo muito fácil de fazer coisas realmente interessantes.Também existem interfaces para os Serviços na camada de negócios aqui.
  • Por fim, coloco todos os objetos de domínio na montagem extra, apenas para tornar a base de código um pouco mais limpa e mais flexível.Esses objetos não possuem nenhuma lógica, eles são apenas uma forma comum de descrever os dados para todas as 3+ camadas.

Por falar nisso.Por que você definiria métodos na camada lógica de negócios para acomodar a camada de dados?
A camada de dados não deve ter motivos para saber que existe uma camada de negócios.

O que você quer dizer com que a camada de dados não deve estar ciente da camada lógica de negócios?Como você preencheria um objeto de negócios com dados?

Costumo fazer isso:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}

Então o problema é que a camada de negócios precisa expor mais funcionalidades à camada de dados, e adicionar essa funcionalidade significa expor demais a camada de UI?Se estou entendendo seu problema corretamente, parece que você está tentando satisfazer demais com uma única interface, e isso está apenas fazendo com que ele fique confuso.Por que não ter duas interfaces na camada de negócios?Uma seria uma interface simples e segura para a camada UI.A outra seria uma interface de nível inferior para a camada de dados.

Você pode aplicar essa abordagem de duas interfaces a quaisquer objetos que precisem ser passados ​​​​para a interface do usuário e também para as camadas de dados.

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

Você pode querer dividir suas interfaces em dois tipos, a saber:

  • Visualizar interfaces - que são interfaces que especificam suas interações com sua UI, e
  • Interfaces de dados – que são interfaces que permitirão especificar interações com seus dados

É possível herdar e implementar ambos os conjuntos de interfaces de forma que:

public class BusinessObject : IView, IData

Dessa forma, na sua camada de dados você só precisa ver a implementação da interface do IData, enquanto na sua UI você só precisa ver a implementação da interface do IView.

Outra estratégia que você pode querer usar é compor seus objetos na interface do usuário ou nas camadas de dados de forma que sejam meramente consumidos por essas camadas, por exemplo,

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

Isso, por sua vez, permite que seu objeto de negócios permaneça ignorante tanto da camada UI/Visualização quanto da camada de dados.

Vou continuar com meu hábito de ir contra a corrente e dizer que você deveria questionar por que está construindo todas essas camadas de objetos terrivelmente complexas.

Acho que muitos desenvolvedores pensam no banco de dados como uma simples camada de persistência para seus objetos e estão preocupados apenas com as operações CRUD que esses objetos precisam.Muito esforço está sendo colocado na “incompatibilidade de impedância” entre os modelos objeto e relacional.Aqui está uma ideia:pare de tentar.

Escreva procedimentos armazenados para encapsular seus dados.Use conjuntos de resultados, DataSet, DataTable, SqlCommand (ou java/php/qualquer equivalente) conforme necessário do código para interagir com o banco de dados.Você não precisa desses objetos.Um excelente exemplo é incorporar um SqlDataSource em uma página .ASPX.

Você não deve tentar ocultar seus dados de ninguém.Os desenvolvedores precisam entender exatamente como e quando estão interagindo com o armazenamento físico de dados.

Os mapeadores objeto-relacionais são o diabo.Pare de usá-los.

A criação de aplicativos corporativos costuma ser um exercício de gerenciamento da complexidade.Você tem que manter as coisas o mais simples possível, ou terá um sistema absolutamente impossível de manter.Se você estiver disposto a permitir algum acoplamento (que é inerente a qualquer aplicativo de qualquer maneira), poderá eliminar a camada de lógica de negócios e a camada de acesso a dados (substituindo-as por procedimentos armazenados) e não precisará de nenhum desses interfaces.

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