Skip to content

labcabrera/sample-spring-mongo-ddd-extension

Repository files navigation

Extendiendo un modelo común de MongoDB con Spring Data

Introducción

Este ejemplo tiene por objetivo mostrar un ejemplo en el que varios microservicios extienden de un modelo común, reutilizando varios componentes (acceso a datos, seguridad, etc) definidos a nivel del modelo general.

Este escenario se daría cuando por ejemplo tenemos una familia de aplicaciones que utilizan una estructura similar de entidades (contratos, entidades legales, etc), en las que cada una de ella requiere ciertas modificaciones para ajustarse a cada negocio específico. Esta filosofía de DDD no suele llevarse demasiado bien con el paradigma de los microservicios, donde cada uno de ellos debería reducir su ámbito del modelo a un subconjunto limitado de entidades. Pero en la práctica, cuando trabajamos con modelos complejos suele ser recomendable extender de una definición común para evitar que cada módulo tenga que definir toda su estructura, algo que también complica bastante las actualizaciones del modelo canónico, ya que cualquier cambio deberá ser ajustado en cada uno de los microservicios.

En nuestro caso trabajaremos con un modelo muy pequeño: contratos, clientes y las relaciones entre ellos. Después tendremos una aplicación que deberá extender este modelo para incluir campos específicos.

Tecnologías

  • Spring Boot 2

  • Spring Data Mongo

  • QueryDsl

  • Swagger

  • JWT

  • HATEOAS

Definicion del modelo

La definición del modelo está en el proyecto commons-model.

En nuestro ejemplo sólamente tenemos las anotaciones de MongoDB, aunque este debería ser agnóstico al modelo de persistencia y en determinados casos podríamos tener también anotaciones de JPA, algo que para evitar complicar el código no he incluido.

Tenemos diferentes opciones a la hora de extender el modelo. Las principales serían:

  • Extender las entidades.

  • Utilizar la composición de entidades.

  • Utilizar genéricos para establecer la información adicional (método utilizado en este ejemplo).

En el primer caso tendríamos:

public class Contract { // canonical model
}

public class CustomContract extends Contract {
}

En el segundo caso tendríamos:

public class Contract { // canonical model
}

public class CustomContract {
  private Contract contract;
  // other specific fields here
}

En el tercer caso tendríamos:

public class Contract<E> {
  // some fields
  private E additionalData;
}

public class CustomContractAdditionalData {
}

Repositorios

Los repositorios están definidos en el proyecto commons-data.

Nuestros repositorios de entidades extenderán tanto de PagingAndSortingRepository como de QuerydslPredicateExecutor por la misma razón que antes: queremos que sean independientes de nuestra implementación. Por ello evitamos extender directamente de MongoRepository.

Modelo de seguridad en la lectura de recursos

En nuestro ejemplo todas las entidades tendrán unas determinadas restricciones de acceso. Esto lo implementamos a nivel de la siguiente interface:

public interface HasAuthorization {

  List<String> getAuthorization();

}

La principal razón de hacerlo de este modo es desacoplar la lógica de negocio específica a la hora de realizar las consultas a nivel de cada entidad. Esto nos permite en función de las granted authorities obtenidas del contexto de seguridad incluir un sistema homogéneo para todas las entidades a través de un Predicate de QueryDsl.

Esto lo realizamos a través de la clase AbstractSecurityService que es la que utilizaremos para las APIs de lectura de recursos.

APIs REST comunes

En el proyecto commons-api hemos definido los componentes comunes a los diferentes microservicios, así como las interfaces generales de lectura.

Utilizando JWT para la securización de nuesta APIs

JWT está consolidado como uno de los principales mecanismos de seguridad para APIs REST y permite de un modo sencillo definir la estructura de permisos que utilizaremos internamente para filtrar las entidades accesibles.

Para obtener el token en nuestra aplicación de ejemplo simplemente deberemos enviar una petición POST con autenticación básica:

curl -X POST -u demo1:demo1 http://localhost:8080/auth -v

Esto nos devolverá un token JWT como una de las cabeceras de respuesta:

< HTTP/1.1 200
< Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1NDE3ODI...
...

RSQL

A la hora de proveer los parámetros de consulta deseamos que nuestra API sea lo más homogénea posible, evitando que cada controller defina una serie arbitraria de @RequestParam para filtrar los resultados. Para ello todas las operaciones de consulta simplemente definirán un único parámetros de búsqueda llamado search que será una expresión RSQL que posteriormente transformaremos a un objeto Predicate de QueryDdl.

  @GetMapping
  // other annotations here
  ResponseEntity<PagedResources<CustomerResource>> find(
    @RequestParam(value = "search", required = false, defaultValue = "") String search,
    Pageable pageable);

HATEOAS

En este ejemplo también utilizaremos Spring HATEOAS para la exposición de nuestra API con el fin de acercarnos lo más posible al Glory of Rest.

Swagger

Para la generación de la documentación de nuestra API utilizaremos la librería SpringFox que se integra fácilmente con Spring como podría uno pensar mirando solamente el nombre.

Únicamente tendremos que definir un bean de tipo Docket donde definiremos las propiedades de generación de la documentación. Después simplemente accederemos al recurso /swagger-ui.html de cada servicio para acceder al explorador de Swagger.

Releases

No releases published

Packages

No packages published

Languages