UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
CURSO DE DISEÑO DE ARQUITECTURA DE SOFTWARE 2024-2
TALLER DE ARQUITECTURA HEXAGONAL CON ANGULAR
En este taller se construirá una funcionalidad básica de producto basada en
arquitectura Hexagonal con lenguaje Angular, al finalizar este taller el alumno
entenderá los fundamentos de la arquitectura hexagonal, la implementación de
esta arquitectura con lenguaje Angular y la diferencia con otras arquitecturas como
MVC.
Estructura del Proyecto.
Product1-hexagonal/
├── app/
│ ├── application/
│ │ └── [Link]
│ ├── domain/
│ │ ├── [Link]
│ │ └── [Link]
│ ├── infrastructure/
│ │ └── [Link]
│ ├── ui/
│ │ └── product-list
│ │ │── [Link]
│ │ └── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── [Link]
└── [Link]
1. Crear el proyecto en el terminal del contenedor.
rails new product1-hexagonal –routing=true –style=scss
cd product1-hexagonal
2. Crear la estructura de carpetas en el proyecto.
2.1 Desde el terminal del contenedor.
mkdir -p src/app/{domain,application,infrastructure,ui}
2.2 desde el terminal del anfitrión (WSL), modificar los permisos de las carpetas
sudo chown -R jgonzales:jgonzales *
3. Construcción de la capa Dominio
3.1 Creamos la entidad Product independiente de la infraestructura.
product1-hexagonal/src/app/domain/[Link]
Mg. Julio César Gonzales Paico
UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
export class Product{
constructor(
public id: number,
public name: string,
public description: string,
public price: number
){}
}
3.2 Definimos el repositorio (interfaz) para la entidad Product.
src/app/domain/[Link]
import { Product } from "./[Link]";
export abstract class ProductRepository{
abstract getProducts(): Promise<Product[]>;
abstract getProductById(id: number): Promise<Product|undefined>;
abstract createProduct(product: Product): Promise<void>;
abstract updateProduct(product: Product): Promise<void>;
abstract deleteProduct(id: number): Promise<void>;
}
4. Construcción de la capa Infraestructura
4.1 Implementamos el repositorio.
src/app/infrastructure/[Link]
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Product } from '../domain/[Link]';
import { ProductRepository } from '../domain/[Link]';
@Injectable({
providedIn: 'root',
})
export class ProductApiRepository extends ProductRepository {
private readonly apiUrl =
'[Link]
[Link]';
constructor(private http: HttpClient) {
super();
}
override getProducts(): Promise<Product[]> {
return [Link]<Product[]>([Link]).toPromise().then(products
=> products || []);;
}
override getProductById(id: number): Promise<Product | undefined> {
return [Link]<Product>(`${[Link]}/${id}`).toPromise();
}
override createProduct(product: Product): Promise<void> {
return [Link]<void>([Link], product).toPromise();
}
override updateProduct(product: Product): Promise<void> {
Mg. Julio César Gonzales Paico
UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
return [Link]<void>(`${[Link]}/${[Link]}`,
product).toPromise();
}
override deleteProduct(id: number): Promise<void> {
return [Link]<void>(`${[Link]}/${id}`).toPromise();
}
}
5. Construimos la capa Aplicación
src/app/application/[Link]
import { Injectable } from '@angular/core';
import { Product } from '../domain/[Link]';
import { ProductRepository } from '../domain/[Link]';
@Injectable({
providedIn: 'root',
})
export class GetProductsUseCase {
constructor(private productRepository: ProductRepository) {}
execute(): Promise<Product[]> {
return [Link]();
}
}
6. Construimos componentes de la capa de interfaz de usuario.
6.1 desde el terminal del contenedor, creamos el componente lista de productos
cd src/app
ng generate component ui/product-list
src/app/ui/product-list/[Link]
import { Component, OnInit } from '@angular/core';
import { GetProductsUseCase } from '../../application/get-
[Link]';
import { Product } from '../../domain/[Link]';
import { ProductRepository } from '../../domain/[Link]';
import { ProductApiRepository } from '../../infrastructure/product-
[Link]';
@Component({
selector: 'app-product-list',
providers:[{ provide: ProductRepository, useClass: ProductApiRepository }
],
templateUrl: './[Link]',
styleUrl: './[Link]'
})
export class ProductListComponent implements OnInit{
products: Product[]=[];
constructor(private getProductUseCase: GetProductsUseCase){}
ngOnInit(): void {
Mg. Julio César Gonzales Paico
UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
[Link]().then(products =>{
[Link]=products;
}
);
}
}
src/app/ui/product-list/[Link]
<p>Lista de Productos!</p>
<!-- src/app/ui/product-list/[Link] -->
<div *ngIf="[Link] > 0; else noProducts">
<ul>
<li *ngFor="let product of products">
{{ [Link] }} - ${{ [Link] }}
</li>
</ul>
</div>
<ng-template #noProducts>
<p>No hay productos disponibles.</p>
</ng-template>
6.2 Modificamos el componente principal
src/app/[Link]
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './[Link]',
styleUrl: './[Link]'
})
export class AppComponent {
title = 'product1-hexagonal';
}
src/app/[Link]
<h1>Arquitectura Hexagonal! Product</h1>
<app-product-list></app-product-list>
6.2 Modificar el index del proyecto.
src/[Link]
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Product1Hexagonal</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="[Link]">
</head>
Mg. Julio César Gonzales Paico
UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
<body>
<app-root></app-root>
</body>
</html>
7. Configuraciones de la aplicación.
src/app/[Link]
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './ui/product-list/product-
[Link]';
const routes: Routes = [
{ path: '', component: ProductListComponent },
];
@NgModule({
imports: [[Link](routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
src/app/[Link]
import { ApplicationConfig, provideZoneChangeDetection } from
'@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './[Link]';
export const appConfig: ApplicationConfig = {
providers: [provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes)]
};
src/app/[Link]
import { HttpClient, HttpClientModule } from "@angular/common/http";
import { NgModule } from "@angular/core";
import { ProductListComponent } from "./ui/product-list/product-
[Link]";
import { AppComponent } from "./[Link]";
import { ProductRepository } from "./domain/[Link]";
import { ProductApiRepository } from "./infrastructure/product-
[Link]";
import { BrowserModule } from "@angular/platform-browser";
import { AppRoutingModule } from "./[Link]";
import { RouterModule } from "@angular/router";
import { CommonModule } from "@angular/common";
@NgModule({
declarations: [AppComponent,
ProductListComponent
],
imports: [
BrowserModule,
AppRoutingModule,
Mg. Julio César Gonzales Paico
UNIVERSIDAD NACIONAL AGRARIA DE LA SELVA – FACULTAD DE INGENIERÍA EN
INFORMÁTICA Y SISTEMAS
RouterModule,
CommonModule,
HttpClientModule
],
providers: [
{provide: ProductRepository, useClass: ProductApiRepository},
],
bootstrap: [AppComponent]
})
export class AppModule{}
src/app/[Link]
import { Routes } from '@angular/router';
export const routes: Routes = [];
src/[Link]
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/[Link]';
platformBrowserDynamic().bootstrapModule(AppModule) .catch(err =>
[Link](err));
8. Ejecutamos la aplicación
ng serve --host [Link] --port 4201
Mg. Julio César Gonzales Paico