This post is also available in english.

Posts en esta serie:

Context Mapping

En un sistema grande hay múltiples Bounded Contexts que colaboran entre si. Algunos son desarrollados por equipos de la empresa y otros son sistemas legacy o sistemas de terceros. Si los equipos no conocen bien los contextos que hay y como se relacionan entre sí se corre el riesgo de comprometer los modelos y generar un Big Ball of Mud.

Las relaciones entre los equipos también es importante, no todos los equipos están motivados por las mismas fuerzas o están alineados en la misma dirección. Se debe manejar la política entre los equipos.

Para combatir esto se suele crear un Context Map (mapa de contextos) que permite visualizar las relaciones a nivel técnico y organizacional entre los distintos Bounded Contexts.

Este mapa permite a todos tener una visión holística permitiendo resolver conflictos tempranamente y evitando corromper la integridad de los modelos de forma accidental. El mapa va evolucionando con el tiempo, es un documento que está vivo.

Primer Context Map

En este primer ejemplo podemos ver que los equipos que desarrollan los contextos de Payroll y Human Resources trabajan en conjunto en un mismo proyecto estableciendo un partnership. Por otro lado el equipo de Payroll se ve influenciado por el contexto de Finance por ende debe modificarse cuando el otro cambia. Finalmente el contexto de Human Resources se integra con un contexto desarrollado por terceros, se adapta a su modelo para evitar el costo de las traducciones.

A continuación vamos a ver distintos elementos que se suelen visibilizar en el Context Map.

Capa anticorrupción (Anti corruption Layer)

Cuando dos modelos necesitan interactuar seguramente utilicen un lenguaje ubícuo distinto y estén modelados de forma completamente distinta. Si no somos cuidadosos al integrar los modelos adaptando las interfaces podemos corromper nuestro modelo introduciendo conceptos ajenos, generando ambigüedades.

Para evitar la corrupción y proteger nuestro modelo de influencias externas se puede crear una capa que contenga interfaces escritas con el lenguaje de nuestro modelo. Esa capa implementa adapters que traducen esos conceptos a los conceptos del otro modelo. Esta capa se conoce como Anticorruption Layer (ACL).

Capa anti-corrupción

En la imágen podemos ver que se crea una pequeña capa que no contiene lógica de negocio sino lógica de traducción. Se adapta el modelo del contexto de Orders al modelo de Commerce para no corromperlo y limitar las dependencias.

Por ejemplo, un contexto de gestión de usuarios podría tener la clase UserAccount con una propiedad roles que podría tener los valores admin, project-manager o developer. Este contexto es genérico, se encarga de gestionar usuarios con roles de forma genérica y se puede utilizar para varios negocios. Podría ser un servicio SaaS desarrollado por terceros.

En un contexto de gestión de proyectos podríamos directamente transformar ese modelo UserAccount en modelos propios de nuestro contexto (ej: clases Developer o ProjectManager). En el contexto de gestión de usuarios los roles son simplemente strings, es genérico y podría servir para crear cuentas de usuario que sirvan para cualquier otro sistema. En cambio nuestro contexto de gestión de proyectos maneja conceptos de negocio donde un Developer o un ProjectManager son clases principales del modelo y tienen lógica de negocio. El ACL se encarga de hacer estas traducciones evitando que nuestro modelo se contamine con el concepto de UserAccount y de roles.

Veamos un ejemplo de código de este caso:

interface DeveloperService {
    fun getDeveloperById(id: Int): Developer
}

// Adapter implementation in the ACL
class UserManagementDeveloperService(
    private val userManagementContext: UserManagementContextAPI,
): DeveloperService {
    override fun getDeveloperById(id: Int): Developer {
        val userAccount = userManagementContext.getUserAccountById(id)
        if (userAccount == null || !userAccount.hasRole("developer")) throw NotFoundError()
        return Developer(id, userAccount.fullName)
    }
}

Núcleo compartido (Shared Kernel)

Si dos equipos trabajan en la misma aplicación en dos bounded contexts que comparten muchos conceptos de dominio y lógica, el overhead de mantener a los equipos aislados y hacer traducciones de uno a otro es muy alto.

En estos casos es mejor colaborar para compartir una porción del modelo. Este modelo compartido se llama Núcleo Compartido (Shared Kernel).

Como hay una dependencia de código hay que tener cuidado porque un equipo puede romper el código del otro. Se necesita que al modificar este código compartido un sistema de integración continua verifique ambos modelos.

Núcleo compartido

Open Host Service / Published Language

A veces un Bounded Context ofrece servicios a otros publicando elementos de su modelo y exponiendo las complejidades internas del mismo. En estos casos a veces solemos ver que cada Bounded Context que consume estos servicios implementa su propia capa ACL haciendo traducciones muy similares entre sí.

En estos casos, para evitar la duplicidad de los ACLs se puede pasar la traducción al Bounded Context que provee el servicio. Estos servicios constituyen una API que utiliza un modelo público que es común para todos los Bounded Contexts. Este modelo público se conoce como Lenguaje Publicado (Published Language) y a esta API se la conoce como Open Host Service.

Para entenderlo mejor podríamos hacer una analogía con los idiomas. Si un Bounded Context habla alemán (su propio lenguaje ubícuo), otro español y otro francés, el Published Language (que todos entienden) sería el ingles (un idioma internacional que se utiliza para comunicarse sin traductores).

Por ejemplo un complejo sistema de ordenes de venta puede requerir traducciones en los sistemas de CRM y comercio. Para evitar esta duplicación el sistema de ordenes puede publicar una versión simplificada directamente en una API.

Los Published Languages deben estar bien documentados y su contrato debe mantenerse lo más estable posible.

Por supuesto que esto también tiene sus contras, pero como todo en nuestra industria, se trata de encontrar el mejor trade-off en cada contexto.

Open Host Service

Política: Caminos Separados (Separate Ways)

Si el costo de integrar es muy grande por cuestiones técnicas o políticas, se puede decidir no hacer ninguna integración. La integración se puede hacer por procesos manuales. Aunque resulte raro en un principio, este caso es muy común. Cuando entra un nuevo empleado en una empresa es normal que se requiera crear su usuario en distintos sistemas, esto se debe a que los mismos no están integrados.

Es importante dejar esto reflejado en el Context Map para que todos sepan de esta decisión.

Política: Partnership

Si 2 equipos son responsables de distintos contextos pero están trabajando por un objetivo común se puede formar un partnership para asegurar cooperación en la integración. Se pueden alinear releases, crear un shared kernel, etc.

Política: Upstream / Downstream

Cuando un cliente depende de un proveedor muchas veces se indican estos roles con la analogía de un rio. El cliente (quien consume los datos o servicios) esta río abajo (downstream) y el proveedor (quien provee los datos o servicios) esta corriente arriba (upstream).

Se puede definir la relación entre contextos en base a la dirección de la misma. El downstream depende de datos o comportamiento del upstream.

Categorizando la relación podemos ver distintos tipos de relaciones entre clientes y proveedores.

Cliente / Proveedor (Customer / Supplier)

En esta relación de upstream/downstream se acuerda un contrato entre las partes. Miembros del equipo que desarrolla el contexto cliente van a las reuniones del equipo del contexto proveedor para asegurarse que sus necesidades y fechas sean entendidas y consideradas.

Customer / Supplier

Conformista (Conformist)

En esta relación el proveedor no colabora con el cliente por ende el cliente se debe conformar. Es una relación típica cuando el proveedor es un servicio externo.

Por ejemplo, un proveedor de un gateway de pagos no va a cambiar su API por una necesidad particular de un cliente.

Ejemplo de un Context Map completo

Context Map

Próximo post

En el próximo post veremos todas las opciones técnicas que tenemos a la hora de integrar Bounded Contexts.