2008-08-12 12 views
11

Digamos que tiene una aplicación dividida en 3 niveles: GUI, lógica de negocios y acceso a datos. En la capa de lógica de negocios que ha descrito sus objetos comerciales: getters, setters, accessors, etc. ... se entiende la idea. La interfaz de la capa de lógica de negocios garantiza el uso seguro de la lógica de negocios, por lo que todos los métodos y accesores que llame validarán la entrada.Interfaces en diferentes capas lógicas

Esto es genial cuando primero escribe el código UI, porque tiene una interfaz claramente definida en la que puede confiar.

Pero aquí viene la parte difícil, cuando empiezas a escribir la capa de acceso a datos, la interfaz con la lógica de negocios no se adapta a tus necesidades. Necesita tener más accesadores y captadores para establecer los campos que están/solían estar ocultos. Ahora está obligado a erosionar la interfaz de su lógica comercial; ahora es posible configurar los campos desde la capa UI, que la capa UI no tiene configuración comercial.

Debido a los cambios necesarios para la capa de acceso a datos, la interfaz con la lógica de negocio se ha erosionado hasta el punto en que es posible establecer incluso la lógica de negocio con datos no válidos. Por lo tanto, la interfaz ya no garantiza un uso seguro.

Espero haber explicado el problema con suficiente claridad. ¿Cómo se evita la erosión de la interfaz, se mantiene la ocultación y el encapsulamiento de la información, y aún así se acomodan las diferentes necesidades de interfaz entre las diferentes capas?

Respuesta

7

Si entiendo la pregunta correctamente, ha creado un modelo de dominio y desea escribir un asignador relacional de objetos para mapear entre registros en su base de datos y sus objetos de dominio. Sin embargo, le preocupa contaminar su modelo de dominio con el código de "fontanería" que sería necesario para leer y escribir en los campos de su objeto.

Dando un paso atrás, esencialmente tiene dos opciones de dónde colocar su código de asignación de datos, dentro de la propia clase de dominio o en una clase de asignación externa. La primera opción a menudo se llama patrón de registro activo y tiene la ventaja de que cada objeto sabe cómo persistir y tiene suficiente acceso a su estructura interna para permitirle realizar la asignación sin necesidad de exponer campos no relacionados con el negocio.

por ejemplo

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 
     // ... 
    } 
} 

En este ejemplo, tenemos un objeto que representa un usuario con un nombre y un accountstatus. No queremos permitir que el Estado se configure directamente, quizás porque queremos verificar que el cambio sea una transición de estado válida, por lo que no tenemos un setter. Afortunadamente, el código de mapeo en los métodos GetById y Save estático tiene acceso completo al nombre del objeto y a los campos de estado.

La segunda opción es tener una segunda clase que sea responsable del mapeo. Esto tiene la ventaja de separar las diferentes preocupaciones de lógica empresarial y persistencia que pueden permitir que su diseño sea más comprobable y flexible. El desafío con este método es cómo exponer los campos de nombre y estado a la clase externa. Algunas opciones son: 1. Use la reflexión (que no tiene reparos en profundizar en las partes privadas de su objeto) 2. Proporcione los setters públicos especialmente nombrados (p. Ej., Prefíquelos con la palabra 'Privado') y espere que nadie los use accidentalmente 3. Si su idioma lo admite, haga que los ajustadores sean internos pero otorgue acceso al módulo de correlación de datos. P.ej. utilice InternalsVisibleToAttribute en .NET 2.0 funciones en adelante o amigos en C++

Para obtener más información, le recomiendo el libro clásico de Martin Fowler Patrones de Arquitectura Empresarial '

Sin embargo, como una palabra de advertencia, antes de ir por el camino de la escritura de su propia mapeadores que recomiendo consultar utilizando una herramienta de mapeo relacional de objetos (ORM) de terceros, como nHibernate o Entity Framework de Microsoft. He trabajado en cuatro proyectos diferentes donde, por diversas razones, escribimos nuestro propio mapeador y es muy fácil perder mucho tiempo manteniendo y extendiendo el mapeador en lugar de escribir código que proporcione valor al usuario final. He utilizado nHibernate en un proyecto hasta el momento y, aunque inicialmente tiene una curva de aprendizaje bastante empinada, la inversión que realiza al principio da buenos resultados.

1

siempre creo un montaje independiente que contiene:

  • Una gran cantidad de interfaces pequeñas (creo ICreateRepository, IReadRepository, IReadListRepsitory .. y la lista continúa y la mayoría de ellos se basa en gran medida en los genéricos)
  • Un Muchas interfaces concretas, como un IPersonRepository, que hereda de IReadRepository, obtienes el punto ...
    Cualquier cosa que no puedas describir solo con las interfaces más pequeñas, la pones en la interfaz concreta.
    Siempre que use el IPersonRepository para declarar su objeto, obtendrá una interfaz limpia y consistente para trabajar. Pero el truco es que también puedes hacer una clase que tenga f.x. un ICreateRepository en su constructor, por lo que el código terminará siendo muy fácil de hacer algunas cosas realmente funky. También hay interfaces para los Servicios en el nivel de negocios aquí.
  • Por fin pegué todos los objetos de dominio en el ensamblaje extra, solo para hacer que la base del código en sí sea un poco más limpia y más flojamente acoplada. Estos objetos no tienen ninguna lógica, solo son una forma común de describir los datos de las 3 capas.

Btw. ¿Por qué definiría los métodos en el nivel lógico de negocios para acomodar el nivel de datos?
El nivel de datos no debería tener ninguna razón para saber siquiera que hay un nivel comercial ...

0

Podría ser una solución, ya que no erosionaría la interfaz. Creo que se puede tener una clase como esta:

public class BusinessObjectRecord : BusinessObject 
{ 
} 
0

¿Qué quiere decir con que el nivel de datos no debe estar al tanto de la capa de lógica de negocio? ¿Cómo llenarías un objeto de negocio con datos?

A menudo hago esto:

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

Este es un problema clásico - la separación de su modelo de dominio de su modelo de base de datos.Hay varias maneras de atacarlo, realmente depende del tamaño de tu proyecto en mi opinión. Podría usar el patrón de repositorio como otros han dicho. Si está utilizando .net o java, puede usar NHibernate o Hibernate.

Lo que hago es usar Test Driven Development, primero escribo mis capas de UI y Modelo y la capa de Datos es burlada, así que la UI y el modelo se construyen alrededor de objetos específicos del dominio, luego asigné estos objetos a la tecnología I ' m usando la capa de datos. Es una muy mala idea dejar que la base de datos determine el diseño de su aplicación, primero escriba la aplicación y piense en los datos más adelante.

ps el título de la pregunta es un poco desencuentro que conducen

1

@ hielo ^^ calor:

¿Qué quiere decir con que el nivel de datos no debe estar al tanto de la capa de lógica de negocio ? ¿Cómo llenarías un objeto de negocio con datos?

La UI pregunta al ServiceClass en el nivel empresarial por un servicio, es decir, obtener una lista de objetos filtrados por un objeto con los datos de parámetros necesarios.
A continuación, ServiceClass crea una instancia de una de las clases de repositorio en el nivel de datos y llama a GetList (filtros ParameterType).
A continuación, el nivel de datos accede a la base de datos, extrae los datos y los asigna al formato común definido en el conjunto "dominio".
El BL no tiene más trabajo que hacer con estos datos, por lo que se lo envía a la interfaz de usuario.

Luego, la interfaz de usuario desea editar el elemento X. Envía el elemento (u objeto comercial) al servicio en el Nivel de negocio. El nivel empresarial valida el objeto y, si está bien, lo envía al nivel de datos para su almacenamiento.

La IU conoce el servicio en el nivel empresarial que, una vez más, conoce el nivel de datos.

La interfaz de usuario es responsable de mapear la entrada de datos de los usuarios hacia y desde los objetos, y el nivel de datos es responsable de mapear los datos en el DB hacia y desde los objetos. El nivel Business se mantiene puramente comercial. :)

0

Entonces, ¿el problema es que la capa empresarial necesita exponer más funcionalidad a la capa de datos, y agregar esta funcionalidad significa exponer demasiado a la capa de la interfaz de usuario? Si entiendo tu problema correctamente, parece que estás tratando de satisfacer demasiado con una sola interfaz, y eso solo está causando que se sature. ¿Por qué no tener dos interfaces en la capa de negocios? Una sería una interfaz simple y segura para la capa de IU. La otra sería una interfaz de nivel inferior para la capa de datos.

Puede aplicar este enfoque de dos interfaces a cualquier objeto que necesite pasar tanto a la interfaz de usuario como a las capas de datos.

public class BusinessLayer : ISimpleBusiness 
{} 

public class Some3LayerObject : ISimpleSome3LayerObject 
{} 
0

Es posible que desee dividir sus interfases en dos tipos, a saber:

  • Ver Interfaces - que son interfaces que especifican sus interacciones con su interfaz de usuario, y
  • interfaces de datos - lo cual son interfaces que le permitirán especificar interacciones con sus datos

Es posible heredar e implementar ambos conjuntos de interfaces de modo que:

public class BusinessObject : IView, IData 

De esta forma, en su capa de datos solo necesita ver la implementación de la interfaz de IData, mientras que en su UI solo necesita ver la implementación de la interfaz de IView.

Otra estrategia es posible que desee utilizar es para componer sus objetos en la interfaz de usuario o capas de datos de tal manera que no son más consumidos por estas capas, por ejemplo,

public class BusinessObject : DomainObject 

public class ViewManager<T> where T : DomainObject 

public class DataManager<T> where T : DomainObject 

Esto a su vez permite que su objeto de negocio permanezca ignorante tanto de la capa UI/View como de la capa de datos.

0

Voy a continuar mi hábito de ir contra la corriente y decir que debes preguntarte por qué estás construyendo todas estas capas de objetos terriblemente complejas.

Creo que muchos desarrolladores piensan en la base de datos como una simple capa de persistencia para sus objetos, y solo se preocupan por las operaciones CRUD que esos objetos necesitan. Se está poniendo demasiado esfuerzo en el "desajuste de impedancias" entre los modelos relacionales y de objetos. Aquí hay una idea: deja de intentarlo.

Escriba procedimientos almacenados para encapsular sus datos. Utilice los conjuntos de resultados, DataSet, DataTable, SqlCommand (o el equivalente java/php/whatever) según sea necesario del código para interactuar con la base de datos. No necesitas esos objetos. Un excelente ejemplo es incorporar un SqlDataSource en una página .ASPX.

No intente ocultar sus datos a nadie. Los desarrolladores deben comprender exactamente cómo y cuándo interactúan con el almacén físico de datos.

Los correladores de objetos relacionales son el diablo. Deja de usarlos.

La construcción de aplicaciones empresariales es a menudo un ejercicio de gestión de la complejidad. Tienes que mantener las cosas lo más simples posible, o tendrás un sistema absolutamente imposible de mantener. Si está dispuesto a permitir algún acoplamiento (que es inherente a cualquier aplicación de todos modos), puede eliminar tanto su capa de lógica de negocios como su capa de acceso a datos (reemplazándolos con procedimientos almacenados), y no necesitará ninguno de esos interfaces.

+0

Supongo que esto podría funcionar si todo lo que hace es colocar una pantalla encima de una tabla de base de datos. –

+0

No, no estoy hablando de aplicaciones que son así de simples. Estoy hablando de escribir procs de grano grueso para manejar toda tu lógica de negocio en aplicaciones empresariales complejas y mantener el código que llama a los procesos lo más simple posible. –

+1

No veo cómo la migración de la lógica empresarial a los procedimientos almacenados ayuda. Preferiría mantenerlos en una buena capa de negocios en un lenguaje que admita buenas pruebas unitarias. –

Cuestiones relacionadas