Pregunta

Supongamos que tiene una aplicación dividida en 3 niveles:GUI, lógica empresarial y acceso a datos.En su capa de lógica empresarial ha descrito sus objetos comerciales:captadores, definidores, descriptores de acceso, etc.entiendes la idea.La interfaz de la capa de lógica empresarial garantiza el uso seguro de la lógica empresarial, por lo que todos los métodos y accesores que llame validarán la entrada.

Esto es fantástico cuando escribes el código de la interfaz de usuario por primera vez, porque tienes una interfaz claramente definida en la que puedes confiar.

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

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

Espero haber explicado el problema con suficiente claridad.¿Cómo se puede evitar la erosión de la interfaz, mantener la información oculta y encapsulada y, aun así, satisfacer las diferentes necesidades de la interfaz entre las diferentes capas?

¿Fue útil?

Solución

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

Dando un paso atrás, básicamente tiene dos opciones sobre dónde colocar su código de mapeo de datos: dentro de la clase de dominio misma o en una clase de mapeo externa.La primera opción a menudo se denomina 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 el mapeo 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 establezca directamente, tal vez porque queremos verificar que el cambio sea una transición de estado válida, por lo que no tenemos un configurador.Afortunadamente, el código de mapeo en los métodos estáticos GetById y Save tiene acceso completo a los campos de nombre y estado del objeto.

La segunda opción es tener una segunda clase que sea responsable del mapeo.Esto tiene la ventaja de separar las diferentes preocupaciones de la lógica empresarial y la persistencia, lo que puede 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 Reflection (que no tiene reparos en profundizar en las partes privadas de su objeto) 2.Proporcionar configuradores públicos con nombres especiales (p. ej.Prefijarlos con la palabra 'privado') y espero que nadie los use accidentalmente 3.Si su idioma lo admite, haga que los configuradores sean internos pero otorgue acceso al módulo de mapeo de datos.P.ej.use InternalsVisibleToAttribute en .NET 2.0 en adelante o funciones amigas en C++

Para obtener más información, recomendaría el libro clásico de Martin Fowler 'Patterns of Enterprise Architecture'.

Sin embargo, como advertencia, antes de seguir el camino de escribir sus propios mapeadores, recomiendo encarecidamente considerar el uso de una herramienta de mapeo relacional de objetos (ORM) de terceros, como nHibernate o Entity Framework de Microsoft.He trabajado en cuatro proyectos diferentes en los que, por diversas razones, escribimos nuestro propio asignador y es muy fácil perder mucho tiempo manteniendo y ampliando el asignador en lugar de escribir código que proporcione valor al usuario final.He usado nHibernate en un proyecto hasta ahora y, aunque inicialmente tiene una curva de aprendizaje bastante pronunciada, la inversión realizada desde el principio vale la pena considerablemente.

Otros consejos

Este es un problema clásico: separar el modelo de dominio del modelo de base de datos.Hay varias formas de atacarlo, en mi opinión, realmente depende del tamaño de su proyecto.Podrías usar el patrón del repositorio como han dicho otros.Si está utilizando .net o java, puede utilizar NHibernate o Hibernar.

Lo que hago es usar Desarrollo basado en pruebas así que primero escribo mis capas de UI y Modelo y se simula la capa de Datos, por lo que la UI y el modelo se construyen alrededor de objetos específicos del dominio, luego asigno estos objetos a cualquier tecnología que esté usando en la Capa de Datos.Es una muy mala idea dejar que la base de datos determine el diseño de su aplicación, escriba la aplicación primero y piense en los datos después.

ps el título de la pregunta es un poco engañoso

@Hielo^^Calor:

¿Qué quiere decir con que el nivel de datos no debería conocer el nivel de lógica empresarial?¿Cómo llenarías un objeto de negocio con datos?

La interfaz de usuario solicita un servicio a ServiceClass en el nivel empresarial, 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 la capa de datos y llama a GetList (filtros ParameterType).
Luego, la capa de datos accede a la base de datos, extrae los datos y los asigna al formato común definido en el ensamblaje del "dominio".
El BL ya no tiene trabajo que hacer con estos datos, por lo que los envía a la interfaz de usuario.

Entonces la interfaz de usuario quiere editar el elemento X.Envía el artículo (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 interfaz de usuario conoce el servicio en el nivel empresarial, que a su vez 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 la capa de datos es responsable de mapear los datos en la base de datos hacia y desde los objetos.El nivel Business sigue siendo puramente empresarial.:)

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:

  • Muchas interfaces pequeñas (piense en ICreateRepository, IReadRepository, IReadListRepsitory...la lista continúa y la mayoría de ellos depende en gran medida de los genéricos)
  • Muchas interfaces concretas, como IPersonRepository, que heredan de IReadRepository, entiendes el punto.
    Todo lo que no pueda describir sólo con las interfaces más pequeñas, lo pone en la interfaz concreta.
    Siempre que utilice IPersonRepository para declarar su objeto, obtendrá una interfaz limpia y consistente con la que trabajar.Pero lo bueno es que también puedes crear una clase que requiera f.x.un ICreateRepository en su constructor, por lo que terminará siendo muy fácil hacer algunas cosas realmente originales con el código.Aquí también se encuentran interfaces para los Servicios en el nivel empresarial.
  • Por fin, pego todos los objetos del dominio en el ensamblaje adicional, solo para que la base del código sea un poco más limpia y esté menos acoplada.Estos objetos no tienen ninguna lógica, son solo una forma común de describir los datos de las 3+ capas.

Por cierto.¿Por qué definiría métodos en el nivel de lógica empresarial para adaptarse al nivel de datos?
El nivel de datos no debería tener ninguna razón para saber siquiera que existe un nivel empresarial.

¿Qué quiere decir con que el nivel de datos no debería conocer el nivel de lógica empresarial?¿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
         {
    }
}

Entonces, ¿el problema es que la capa empresarial necesita exponer más funcionalidad a la capa de datos, y agregar esta funcionalidad significa exponer demasiada a la capa UI?Si entiendo su problema correctamente, parece que está tratando de satisfacer demasiadas cosas con una única interfaz, y eso sólo está provocando que se vuelva desordenada.¿Por qué no tener dos interfaces en la capa empresarial?Una sería una interfaz sencilla y segura para la capa UI.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 deba pasarse tanto a la interfaz de usuario como a las capas de datos.

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

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

  • Ver interfaces, que son interfaces que especifican sus interacciones con su interfaz de usuario, y
  • Interfaces de datos: que 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 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 modo que estas capas simplemente los consuman, 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/Ver como de la capa de datos.

Voy a continuar con mi hábito de ir contra la corriente y decir que deberías preguntarte por qué estás construyendo todas estas capas de objetos horriblemente 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 impedancia" entre los modelos de objetos y relacionales.He aquí una idea:deja de intentar.

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

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

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 cierto 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ándolas con procedimientos almacenados), y no necesitará ninguno de esos. interfaces.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top