software - architecture traduccion
Interfaces en diferentes capas lógicas (9)
¿A qué te refieres con que el nivel de datos no debe tener en cuenta el nivel de lógica de negocios? ¿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
{
}
}
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.
Es genial cuando primero escribes el código UI, porque tienes una interfaz claramente definida en la que puedes confiar.
Pero aquí viene la parte difícil, cuando comienzas a escribir la capa de acceso a datos, la interfaz a 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 incluso establecer 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?
@ Ice ^^ Calor:
¿A qué te refieres con que el nivel de datos no debe tener en cuenta el nivel de lógica de negocios? ¿Cómo llenarías un objeto de negocio con datos?
La interfaz de usuario le pide al ServiceClass en el nivel de negocio para un servicio, es decir, obtener una lista de objetos filtrados por un objeto con los datos de parámetros necesarios.
Luego, ServiceClass crea una instancia de una de las clases de repositorio en el nivel de datos y llama a GetList (filtros ParameterType).
Luego, el nivel de datos accede a la base de datos, extrae los datos y los mapea en el formato común definido en el conjunto "dominio".
El BL ya no tiene más trabajo que hacer con esta información, por lo que la envía a la interfaz de usuario.
A continuación, la interfaz de usuario desea editar el elemento X. Envía el elemento (u objeto comercial) al servicio en el nivel comercial. 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. :)
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 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
{}
Podría ser una solución, ya que no erosionaría la interfaz. Supongo que podrías tener una clase como esta:
public class BusinessObjectRecord : BusinessObject
{
}
Siempre creo un ensamblaje separado que contiene:
- Un montón de pequeñas interfaces (piense en ICreateRepository, IReadRepository, IReadListRepsitory ... la lista continúa y la mayoría de ellas depende en gran medida de los genéricos)
- Una gran cantidad de 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 fx 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í. - Finalmente, 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 unida de forma más flexible. Estos objetos no tienen ninguna lógica, solo son una forma común de describir los datos de las 3 capas.
Por cierto. ¿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 empresarial.
Es posible que desee dividir sus interfaces en dos tipos, a saber:
- Ver interfaces, que son interfaces que especifican sus interacciones con su UI, y
- Interfaces de datos: son interfaces que te permitirán especificar interacciones con tus datos
Es posible heredar e implementar ambos conjuntos de interfaces de manera que:
public class BusinessObject : IView, IData
De esta manera, 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 que quizás desee utilizar es componer sus objetos en la interfaz de usuario o en las capas de datos de forma tal que simplemente sean 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 comercial permanezca ignorante tanto de la capa UI / View como de la capa de datos.
Si entiendo la pregunta correctamente, ha creado un modelo de dominio y le gustaría 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 tienes dos opciones de dónde ubicar tu código de mapeo de datos: dentro de la propia clase de dominio o en una clase de mapeo externo. 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.
P.ej
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 estado de cuenta. 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 instaladores públicos con nombres especiales (por ejemplo, prefíquelos con la palabra ''Privado'') y espere que nadie los use accidentalmente 3 Si su lenguaje lo admite, conviértalo interno pero acceda al módulo de corrector de datos. Por ejemplo, use InternalsVisibleToAttribute en .NET 2.0 en adelante o funciones de amigo en C ++
Para más información, recomendaría el clásico libro de Martin Fowler ''Patterns of Enterprise Architecture''
Sin embargo, como advertencia, antes de seguir por el camino de escribir sus propios mapeadores, recomiendo encarecidamente utilizar una herramienta 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.
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.
Escribe procedimientos almacenados para encapsular tus 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 deberías tratar de ocultar tus datos a nadie. Los desarrolladores deben comprender exactamente cómo y cuándo interactúan con el almacén físico de datos.
Los mapeadores relacionales de objetos son el diablo. Deja de usarlos.
La creación de aplicaciones empresariales suele ser 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.
Este es un problema clásico: separa el modelo de su dominio de su modelo de base de datos. Hay varias formas 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 utilizar Test Driven Development para escribir primero las capas UI y Model y la capa Data se burla, por lo que la UI y el modelo se construyen alrededor de objetos específicos del dominio, luego los mapeo a la tecnología que estoy 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 luego piense en los datos.
ps el título de la pregunta es un poco mal-líder