https://iamwillwang.com/dollar/every-html-element/
La arquitectura hexagonal, también conocida como arquitectura de puertos y adaptadores, es un estilo de diseño de software propuesto por Alistair Cockburn. Su objetivo es aislar el núcleo de la aplicación (la lógica de negocio) del mundo exterior (interfaces de usuario, bases de datos, APIs externas, etc.), creando un diseño más modular, escalable y fácil de probar.
Imaginemos un sistema para gestionar órdenes de compra. Implementaremos:
/Core
- IOrderService.cs (Puerto de entrada)
- IOrderRepository.cs (Puerto de salida)
- OrderService.cs (Implementación del servicio)
- Models/
- Order.cs
/Infrastructure
- DatabaseOrderRepository.cs (Adaptador de salida)
/API
- OrderController.cs (Adaptador de entrada)
public interface IOrderService
{
void CreateOrder(Order order);
Order GetOrderById(Guid orderId);
}
public interface IOrderRepository
{
void Add(Order order);
Order FindById(Guid orderId);
}
public class Order
{
public Guid Id { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public class OrderService : IOrderService
{
private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public void CreateOrder(Order order)
{
// Validaciones de negocio
if (order.Quantity <= 0)
throw new ArgumentException("La cantidad debe ser mayor a cero.");
_orderRepository.Add(order);
}
public Order GetOrderById(Guid orderId)
{
return _orderRepository.FindById(orderId);
}
}
public class DatabaseOrderRepository : IOrderRepository
{
private readonly List<Order> _orders = new List<Order>();
public void Add(Order order)
{
_orders.Add(order);
}
public Order FindById(Guid orderId)
{
return _orders.FirstOrDefault(o => o.Id == orderId);
}
}
[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public IActionResult CreateOrder([FromBody] Order order)
{
_orderService.CreateOrder(order);
return Ok("Orden creada con éxito.");
}
[HttpGet("{id}")]
public IActionResult GetOrderById(Guid id)
{
var order = _orderService.GetOrderById(id);
return order != null ? Ok(order) : NotFound();
}
}
/components
- OrderForm.jsx
- OrderList.jsx
/services
- api.js
api.js
):
const BASE_URL = "http://localhost:5000/api/orders";
export const createOrder = async (order) => {
const response = await fetch(`${BASE_URL}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(order),
});
return response.json();
};
export const getOrderById = async (id) => {
const response = await fetch(`${BASE_URL}/${id}`);
return response.json();
};
OrderForm.jsx
):
import React, { useState } from "react";
import { createOrder } from "../services/api";
const OrderForm = () => {
const [productName, setProductName] = useState("");
const [quantity, setQuantity] = useState(1);
const [price, setPrice] = useState(0);
const handleSubmit = async (e) => {
e.preventDefault();
const order = { productName, quantity, price };
await createOrder(order);
alert("Orden creada con éxito.");
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Nombre del producto"
value={productName}
onChange={(e) => setProductName(e.target.value)}
/>
<input
type="number"
placeholder="Cantidad"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
/>
<input
type="number"
placeholder="Precio"
value={price}
onChange={(e) => setPrice(e.target.value)}
/>
<button type="submit">Crear Orden</button>
</form>
);
};
export default OrderForm;
OrderService
) y puede probarse fácilmente con mocks.DatabaseOrderRepository
.api.js
) desacopla los detalles de implementación del resto del código.Para saber más:
https://salesystems.es/arquitectura-hexagonal/
https://www.hackio.com/blog/que-es-arquitectura-hexagonal-en-programacion
https://medium.com/@edusalguero/arquitectura-hexagonal-59834bb44b7f
Crear un formulario para poner la garantía de un electrodoméstico. Tiene dos campos ‘años’ y ‘precio’. Los dos son requeridos y el primero tiene que estar entre 2 y 5 y el segundo ser mayor de 500.
Cuando le demos a enviar que envíe los datos por post a ‘https://mitienda.com/garantia’
Tenemos la siguiente tabla:
Producto
id, nombre, precio, referencia
Vamos a hacer un CRUD que nos de el mantenimiento completo. Lo hacemos, de momento, con json-server. Podéis ‘buscar inspiración’ en el de alumnos.
Una vez funcione:
Vamos a hacer un CRUD para la siguiente ‘base de datos’
{ "notes": [ { "id": 1, "content": "HTML is easy", "date": "2023-01-03T10:30:11.414Z", "important": false }, { "id": 2, "content": "Browser can execute only JavaScript", "date": "2019-05-30T18:39:34.091Z", "important": false }, { "id": 3, "content": "GET and POST are the most important methods of HTTP protocol", "date": "2023-01-09T09:29:26.131Z", "important": true }, { "content": "Cchuetes", "date": "2022-12-18T20:25:10.598Z", "important": true, "id": 4 }, { "id": 5, "content": "Comprar pan", "date": "2023-01-09T08:46:37.794Z", "important": false }, { "content": "Nueva nota", "date": "2023-01-09T09:29:21.122Z", "important": false, "id": 6 } ] }
De momento solo gestionamos el contenido. La fecha la ponemos automáticamente y si es importante o no lo dejamos de lado, ponemos siempre ‘false’.
import React, { useState, useEffect } from 'react'; import axios from 'axios'; const App = () => { // Estado de los posts. Aquí cargaremos la información de la api // Parecido a la de tareas const [posts, setPosts] = useState([]); // Para el formulario, en este caso todos los campos dentro de un objeto const [nuevoPost, setNuevoPost] = useState({ title: '', body: '' }); // Esto se ejecuta la primera vez useEffect(() => { // Llamamos a la función que carga los posts obtenerPosts(); }, []); // Aquí tenemos tres funciones porque son las tres operaciones que podemos hacer: // Leer, añadir y eliminar. // Es mejorable, pero funciona // Llamamos con axios a la api y ponemos lo obtenido en el estado const obtenerPosts = async () => { try { const response = await axios.get('http://localhost:4000/posts'); setPosts(response.data); } catch (error) { console.error('Error al obtener posts:', error); } }; // LLamamamos a la api con POST y los datos del formulario const agregarPost = async () => { try { await axios.post('http://localhost:4000/posts', nuevoPost); // Ponemos a cero el formulario setNuevoPost({ title: '', body: '' }); // Cargadmos los datos de la api (podríamos hacerlo directamente en el estado) obtenerPosts(); } catch (error) { console.error('Error al agregar post:', error); } }; // Llamamos a la api con DELETE const eliminarPost = async (id) => { try { await axios.delete(`http://localhost:4000/posts/${id}`); // Cargamos los datos de nuevo obtenerPosts(); } catch (error) { console.error('Error al eliminar post:', error); } }; return ( <div> <h1>Posts</h1> <ul> {/* Lista de todos los posts con su 'key */} {posts.map((post) => ( <li key={post.id}> <h2>{post.title}</h2> <p>{post.body}</p> <button onClick={() => eliminarPost(post.id)}>Eliminar</button> </li> ))} </ul> {/** Formulario, el change no llama a ninguna función, directamente cambia el estado en * los dos campos. Yo prefiero hacerlo con una sola función. */} <h2>Agregar nuevo post</h2> <input type="text" value={nuevoPost.title} onChange={(e) => setNuevoPost({ ...nuevoPost, title: e.target.value })} placeholder="Título" /> <textarea value={nuevoPost.body} onChange={(e) => setNuevoPost({ ...nuevoPost, body: e.target.value })} placeholder="Cuerpo" /> <button onClick={agregarPost}>Agregar post</button> </div> ); }; export default App;
db.json
{ "posts": [ { "id": "1", "title": "Post 1", "body": "Cuerpo del post 1" }, { "id": "29d4", "title": "Post 3", "body": "holi" }, { "id": "e4da", "title": "dfsdfsdfsd", "body": "asdasd" } ] }
import React, { useRef } from "react"; function App() { // Crear una referencia con useRef const inputRef = useRef(null); // Función para obtener el valor actual del input const handleGetValue = () => { alert(`Valor actual del input: ${inputRef.current.value}`); }; // Función para cambiar el valor del input const handleChangeValue = () => { inputRef.current.value = "Nuevo valor dinámico"; alert("El valor del input ha sido cambiado"); }; return ( <div> <h1>Ejemplo de obtener y cambiar valor con useRef</h1> {/* Input con referencia */} <input ref={inputRef} type="text" placeholder="Escribe algo aquí..." /> <br /> {/* Botón para obtener el valor */} <button onClick={handleGetValue}>Obtener valor</button> {/* Botón para cambiar el valor */} <button onClick={handleChangeValue}>Cambiar valor</button> </div> ); } export default App;
Vamos a hacer un ejercicio para mantener una lista de la compra, sin API
Tenemos un campo de texto y un botón de añadir. Si escribimos algo y le damos a añadir lo veremos en una tabla debajo de ese botón.
Cada elemento de la lista de la compra tendrá un botón al lado para eliminarlo
Una cosa como esta:
Producto: [_________] [Añadir]
Lista
Patatas [Borrar]
Tomates [Borrar]
Bacon [Borrar]
Pasos a realizar:
¿Cuál es nuestro estado?
¿Qué componentes vamos a tener?
¿Cómo creamos nuestro ‘árbol’ y como intercambiamos la información?
App
import React, { useState } from "react"; import ContextoCon from "./ContextoCon"; import IncrementButton from "./IncrementButton"; import DisplayCount from "./DisplayCount"; function CounterProvider({ children }) { const [count, setCount] = useState(0); return ( <ContextoCon valor={{ count, setCount }}> {children} </ContextoCon> ); } function App() { return ( <CounterProvider> <DisplayCount /> <IncrementButton /> </CounterProvider> ); } export default App;
Contexto
import { createContext } from 'react' const Contexto = createContext({}) export default Contexto;
ContextoCon
import Contexto from "./Contexto"; const ContextoCon= ({children,valor}) => { return <Contexto.Provider value={valor}> {children} </Contexto.Provider> } export default ContextoCon;
IncrementButton
import { useContext } from "react"; import Contexto from "./Contexto"; function IncrementButton() { const { count, setCount } = useContext(Contexto); return <button onClick={() => setCount(count + 1)}>Incrementar</button>; } export default IncrementButton;
DisplayButton
import { useContext } from "react"; import Contexto from "./Contexto"; function DisplayCount() { const { count } = useContext(Contexto); return <p>Contador: {count}</p>; } export default DisplayCount;