De acuerdo con el principio de diseño de desarrollo de software, el software que requiere el mínimo esfuerzo de mantenimiento se considera un buen diseño. Es decir, el mantenimiento debe ser el punto clave que debe considerar un arquitecto. En este artículo, se analiza una de esas arquitecturas, conocida como arquitectura hexagonal, que hace que el software sea fácil de mantener, administrar, probar y escalar.
La arquitectura hexagonal es un término acuñado por Alistair Cockburn en 2006. El otro nombre de la arquitectura hexagonal es arquitectura de puertos y adaptadores . Esta arquitectura divide una aplicación en dos partes, a saber, la parte interior y la parte exterior .. La lógica central de una aplicación se considera como la parte interna. La base de datos , la interfaz de usuario y las colas de mensajería podrían ser la parte exterior. Al hacerlo, la lógica de la aplicación principal se ha aislado completamente del mundo exterior. Ahora la comunicación entre estas dos partes puede ocurrir a través de puertos y adaptadores. Ahora, entendamos lo que significa cada uno de estos.
- Los Puertos: Los Puertos actúan como una puerta de enlace a través de la cual se lleva a cabo la comunicación como un puerto de entrada o salida. Un puerto de entrada es algo así como una interfaz de servicio que expone la lógica central al mundo exterior. Un puerto de salida es algo así como una interfaz de repositorio que facilita la comunicación entre la aplicación y el sistema de persistencia.
- Los adaptadores: los adaptadores actúan como una implementación de un puerto que maneja la entrada del usuario y la traduce a la llamada específica del idioma. Básicamente encapsula la lógica para interactuar con sistemas externos como colas de mensajes, bases de datos, etc. También transforma la comunicación entre los objetos externos y el núcleo. Los adaptadores son de nuevo de dos tipos.
- Adaptadores primarios: impulsa la aplicación utilizando el puerto de entrada de una aplicación y también se denomina adaptadores de conducción . Ejemplos de adaptadores primarios podrían ser WebViews o Rest Controllers.
- Adaptadores secundarios: esta es una implementación de un puerto de salida controlado por la aplicación y también llamado adaptadores controlados . La conexión con colas de mensajería, bases de datos y llamadas API externas son algunos de los ejemplos de adaptadores secundarios.
Por lo tanto, la arquitectura hexagonal habla de exponer múltiples puntos finales en una aplicación con fines de comunicación. Si tenemos el adaptador adecuado para nuestro puerto, nuestra solicitud será entretenida. Esta arquitectura es una arquitectura en capas y consta principalmente de tres capas, Marco, Aplicación y Dominio.
- Dominio: es una capa de lógica comercial central y los detalles de implementación de las capas externas están ocultos con esto.
- Aplicación: Actúa como mediador entre la capa de Dominio y la capa de Framework.
- Marco: esta capa tiene todos los detalles de implementación de cómo una capa de dominio interactuará con el mundo externo.
Ejemplo Ilustrativo: Entendamos esta arquitectura con un ejemplo en tiempo real. Diseñaremos una aplicación Cake Service usando Spring Boot. También puede crear un proyecto normal basado en Spring o Maven, según su conveniencia. Las siguientes son las diferentes partes en el ejemplo:
- Dominio: Núcleo de la aplicación. Cree una clase Cake con sus atributos, para mantenerlo simple solo agregaremos el nombre aquí.
Java
// Consider this as a value object // around which the domain logic revolves. public class Cake implements Serializable { private static final long serialVersionUID = 100000000L; private String name; // Getters and setters for the name public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Cake [name=" + name + "]"; } }
- Puerto de entrada: Defina una interfaz a través de la cual nuestra aplicación principal permitirá su comunicación. Expone la aplicación central al mundo exterior.
Java
import java.util.List; // Interface through which the core // application communicates. For // all the classes implementing the // interface, we need to implement // the methods in this interface public interface CakeService { public void createCake(Cake cake); public Cake getCake(String cakeName); public List<Cake> listCake(); }
- Puerto de salida: cree una interfaz más para crear o acceder al mundo exterior, es decir, Cake.
Java
import java.util.List; // Interface to access the cake public interface CakeRepository { public void createCake(Cake cake); public Cake getCake(String cakeName); public List<Cake> getAllCake(); }
- Adaptadores principales: un controlador podría ser nuestro adaptador principal que proporcionará puntos finales para crear y obtener los recursos.
Java
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; // This is the REST endpoint @RestController @RequestMapping("/cake") public class CakeRestController implements CakeRestUI { @Autowired private CakeService cakeService; @Override public void createCake(Cake cake) { cakeService.createCake(cake); } @Override public Cake getCake(String cakeName) { return cakeService.getCake(cakeName); } @Override public List<Cake> listCake() { return cakeService.listCake(); } }
- Podemos crear una interfaz más para CakeRestUI de la siguiente manera:
Java
import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; public interface CakeRestUI { @PostMapping void createCake(@RequestBody Cake cake); @GetMapping("/{name}") public Cake getCake(@PathVariable String name); @GetMapping public List<Cake> listCake(); }
- Adaptadores secundarios: Esta será la implementación de un puerto de salida. Dado que CakeRepository es nuestro puerto de salida, vamos a implementarlo.
Java
import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.stereotype.Repository; // Implementing the interface and // all the methods which have been // defined in the interface @Repository public class CakeRepositoryImpl implements CakeRepository { private Map<String, Cake> cakeStore = new HashMap<String, Cake>(); @Override public void createCake(Cake cake) { cakeStore.put(cake.getName(), cake); } @Override public Cake getCake(String cakeName) { return cakeStore.get(cakeName); } @Override public List<Cake> getAllCake() { return cakeStore.values().stream().collect(Collectors.toList()); } }
- Comunicación entre el núcleo y la fuente de datos: finalmente, creemos una clase de implementación que será responsable de la comunicación entre la aplicación central y la fuente de datos mediante un puerto de salida.
Java
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // This is the implementation class // for the CakeService @Service public class CakeServiceImpl implements CakeService { // Overriding the methods defined // in the interface @Autowired private CakeRepository cakeRepository; @Override public void createCake(Cake cake) { cakeRepository.createCake(cake); } @Override public Cake getCake(String cakeName) { return cakeRepository.getCake(cakeName); } @Override public List<Cake> listCake() { return cakeRepository.getAllCake(); } }
Finalmente hemos implementado todos los métodos requeridos en el ejemplo dado. El siguiente es el resultado de ejecutar el código anterior:
Ahora, creemos un pastel para el ejemplo anterior usando la API REST. La siguiente API se usa para enviar los pasteles al repositorio. Como estamos creando y agregando los datos, usamos la solicitud POST. Por ejemplo:
- API: [ POST ]: http://localhost:8080/cake
Cuerpo de entrada
{ "name" : "Black Forest" }
- API: [ POST ]: http://localhost:8080/cake
Cuerpo de entrada
{ "name" : "Red Velvet" }
- API: [ GET ]: http://localhost:8080/cake
Salida
[ { "name": "Black Forest" }, { "name": "Red Velvet" } ]
Ventajas de la arquitectura Hexagonal:
- Fácil de mantener: dado que la lógica de la aplicación central (clases y objetos) está aislada del mundo exterior y está débilmente acoplada, es más fácil de mantener. Es más fácil agregar algunas características nuevas en cualquiera de las capas sin tocar la otra.
- Fácil de adaptar nuevos cambios: Dado que todas las capas son independientes y si queremos agregar o reemplazar una nueva base de datos, solo necesitamos reemplazar o agregar los adaptadores de base de datos, sin cambiar la lógica de dominio de una aplicación.
- Fácil de probar: la prueba se vuelve fácil. Podemos escribir los casos de prueba para cada capa simplemente simulando los puertos usando los adaptadores simulados.
Publicación traducida automáticamente
Artículo escrito por asadaliasad y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA