martes, 3 de junio de 2025

Resumen y ejemplo DDD

 

Resumen y ejemplo sobre Domain-Driven Design (DDD)

El Domain-Driven Design (DDD) es un enfoque para el desarrollo de software que prioriza el dominio del negocio sobre la tecnología. Propone una estrecha colaboración entre expertos técnicos y de dominio para crear un modelo que refleje fielmente los procesos de negocio.



Conceptos clave de DDD

  1. Lenguaje Ubicuo: Vocabulario común compartido entre desarrolladores y expertos del dominio.
  2. Modelo de Dominio: Representación abstracta del dominio del negocio y sus reglas.
  3. Contextos Delimitados: Fronteras explícitas donde un modelo particular es válido.
  4. Entidades, Value Objects, Agregados: Bloques de construcción del modelo.
  5. Servicios de Dominio: Operaciones que no pertenecen naturalmente a entidades o value objects.
  6. Eventos de Dominio: Notificaciones de cambios significativos en el dominio.
  7. Repositorios: Abstracción para persistencia de agregados.

Cómo implementar DDD con éxito

  1. Inmersión en el dominio:
    • Organiza sesiones de Event Storming con expertos del dominio
    • Documenta procesos y terminología del negocio
    • Identifica subdominos (core, supporting, generic)
  2. Diseño del modelo:
    • Define contextos delimitados con claridad
    • Establece un lenguaje ubicuo documentado
    • Identifica entidades, value objects y agregados
    • Diseña reglas de negocio en el dominio, no en aplicación
  3. Arquitectura hexagonal:
    • Separa el dominio de la infraestructura
    • Implementa puertos y adaptadores
    • Usa inyección de dependencias
  4. Implementación práctica:
    • Crea clases limpias con responsabilidades claras
    • Implementa validaciones a nivel de dominio
    • Usa eventos para comunicación entre agregados
    • Mantén agregados pequeños y cohesivos
  5. Refinamiento continuo:
    • Refactoriza según evoluciona el conocimiento del dominio
    • Revisa regularmente con expertos del dominio
    • Actualiza el lenguaje ubicuo según sea necesario


Voy a crear un diagrama completo para ilustrar estos conceptos en un ejemplo práctico.



Arquitectura hexagonal

Contexto y lenguaje ubicuo

Explicación del Ejemplo Práctico

He creado un ejemplo completo de una aplicación para gestión de cursos educativos usando Domain-Driven Design. Revisemos cada diagrama y código para entender cómo implementarlo correctamente:

1. Modelo de Dominio

El diagrama de clases muestra la estructura del modelo de dominio con:

  • Entidades: Curso, Estudiante, Inscripción
  • Value Objects: CursoId, EstudianteId, InscripcionId, Precio
  • Enumeraciones: EstadoInscripcion
  • Repositorios: Interfaces para persistencia
  • Eventos: InscripcionCompletadaEvent

Observa cómo cada entidad tiene su propia identidad (ID) implementada como value object, y cómo se establecen relaciones entre agregados mediante referencias por ID.

2. Arquitectura Hexagonal

El segundo diagrama muestra la arquitectura hexagonal (o de puertos y adaptadores) que complementa perfectamente el DDD:

  • Centro: El dominio contiene todas las reglas de negocio
  • Capa de Aplicación: Orquesta casos de uso usando el dominio
  • Infraestructura: Implementa interfaces del dominio (adaptadores secundarios)
  • Interfaces de Usuario: Puntos de entrada a la aplicación (adaptadores primarios)

Esta separación permite que el dominio se mantenga puro y libre de dependencias externas.

3. Contextos Delimitados y Lenguaje Ubicuo

El tercer diagrama muestra cómo dividir un sistema complejo en contextos delimitados:

  • Gestión de Cursos
  • Gestión de Estudiantes
  • Inscripciones
  • Pagos

Cada contexto tiene su propio modelo y terminología. Las líneas punteadas representan los mapeos y traducciones necesarios entre contextos.

4. Implementación en Código

El código de ejemplo muestra:

  1. Value Objects inmutables (CursoId, Precio)
  2. Entidades con identidad y comportamiento (Curso)
  3. Reglas de negocio encapsuladas dentro del dominio
  4. Invariantes protegidos con validaciones
  5. Interfaces de repositorio definidas en el dominio
  6. Servicios de dominio para orquestar operaciones entre agregados
  7. Eventos de dominio para comunicación entre agregados
  8. Capas de aplicación que implementan casos de uso
  9. Adaptadores que conectan con el mundo exterior

Pasos para implementar DDD con éxito

  1. Comienza con Event Storming: Reúne expertos en dominio y técnicos para mapear eventos clave.
  2. Define contextos delimitados: Identifica los límites naturales en tu dominio.
  3. Construye el lenguaje ubicuo: Documenta términos y conceptos clave en un glosario.
  4. Modela el dominio: Identifica agregados, entidades y value objects.
  5. Diseña para el comportamiento: Coloca reglas de negocio dentro de las entidades y agregados.
  6. Utiliza arquitectura hexagonal: Separa claramente el dominio de la infraestructura.
  7. Implementa persistencia con repositorios: Define interfaces en el dominio.
  8. Utiliza eventos de dominio: Para comunicación desacoplada entre agregados.
  9. Mantén los agregados pequeños: Cada agregado debe tener una sola responsabilidad.
  10. Refina continuamente: DDD es un proceso iterativo que mejora con el aprendizaje.

 

Anexo

Pasando a código:

// -- ENTIDADES Y VALUE OBJECTS --

 

// Value Object: CursoId

public class CursoId {

    private final UUID valor;

   

    public CursoId(UUID valor) {

        this.valor = Objects.requireNonNull(valor, "El ID no puede ser nulo");

    }

   

    public static CursoId generar() {

        return new CursoId(UUID.randomUUID());

    }

   

    @Override

    public boolean equals(Object o) {

        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        CursoId cursoId = (CursoId) o;

        return valor.equals(cursoId.valor);

    }

   

    @Override

    public int hashCode() {

        return Objects.hash(valor);

    }

}

 

// Value Object: Precio

public class Precio {

    private final BigDecimal monto;

    private final String moneda;

   

    public Precio(BigDecimal monto, String moneda) {

        if (monto.compareTo(BigDecimal.ZERO) < 0) {

            throw new IllegalArgumentException("El precio no puede ser negativo");

        }

       

        this.monto = monto;

        this.moneda = Objects.requireNonNull(moneda, "La moneda no puede ser nula");

    }

   

    public Precio aplicarDescuento(BigDecimal porcentaje) {

        if (porcentaje.compareTo(BigDecimal.ZERO) < 0 || porcentaje.compareTo(new BigDecimal("100")) > 0) {

            throw new IllegalArgumentException("El porcentaje debe estar entre 0 y 100");

        }

       

        BigDecimal factor = BigDecimal.ONE.subtract(porcentaje.divide(new BigDecimal("100")));

        BigDecimal nuevoMonto = this.monto.multiply(factor).setScale(2, RoundingMode.HALF_UP);

        return new Precio(nuevoMonto, this.moneda);

    }

   

    public BigDecimal getMonto() {

        return monto;

    }

   

    public String getMoneda() {

        return moneda;

    }

}

 

// Entidad: Curso (Agregado Root)

public class Curso {

    private final CursoId id;

    private String titulo;

    private String descripcion;

    private int capacidadMaxima;

    private Set<Inscripcion> inscripciones;

   

    public Curso(CursoId id, String titulo, String descripcion, int capacidadMaxima) {

        this.id = Objects.requireNonNull(id, "El ID no puede ser nulo");

        this.titulo = Objects.requireNonNull(titulo, "El título no puede ser nulo");

        this.descripcion = Objects.requireNonNull(descripcion, "La descripción no puede ser nula");

       

        if (capacidadMaxima <= 0) {

            throw new IllegalArgumentException("La capacidad máxima debe ser mayor que cero");

        }

       

        this.capacidadMaxima = capacidadMaxima;

        this.inscripciones = new HashSet<>();

    }

   

    public static Curso crear(String titulo, String descripcion, int capacidadMaxima) {

        return new Curso(CursoId.generar(), titulo, descripcion, capacidadMaxima);

    }

   

    public Inscripcion inscribirEstudiante(Estudiante estudiante, Precio precio) {

        if (inscripciones.size() >= capacidadMaxima) {

            throw new DominioExcepcion("El curso ha alcanzado su capacidad máxima");

        }

       

        // Verificar si el estudiante ya está inscrito

        boolean yaInscrito = inscripciones.stream()

            .anyMatch(i -> i.getEstudianteId().equals(estudiante.getId()) &&

                    !i.getEstado().equals(EstadoInscripcion.CANCELADA));

                   

        if (yaInscrito) {

            throw new DominioExcepcion("El estudiante ya está inscrito en este curso");

        }

       

        Inscripcion inscripcion = Inscripcion.crear(this.id, estudiante.getId(), precio);

        inscripciones.add(inscripcion);

       

        // Publicar evento de dominio

        EventBus.publicar(new EstudianteInscritoEvent(inscripcion.getId()));

       

        return inscripcion;

    }

   

    public void cancelarInscripcion(InscripcionId inscripcionId) {

        Inscripcion inscripcion = buscarInscripcion(inscripcionId);

        inscripcion.cancelar();

       

        // Publicar evento de dominio

        EventBus.publicar(new InscripcionCanceladaEvent(inscripcionId));

    }

   

    private Inscripcion buscarInscripcion(InscripcionId inscripcionId) {

        return inscripciones.stream()

            .filter(i -> i.getId().equals(inscripcionId))

            .findFirst()

            .orElseThrow(() -> new DominioExcepcion("Inscripción no encontrada"));

    }

   

    // Getters y otros métodos

}

 

// -- REPOSITORIOS (INTERFACES) --

 

public interface CursoRepository {

    void guardar(Curso curso);

    Optional<Curso> buscarPorId(CursoId id);

    List<Curso> buscarPorTitulo(String titulo);

}

 

// -- SERVICIOS DE DOMINIO --

 

public class ServicioInscripcion {

    private final CursoRepository cursoRepository;

    private final EstudianteRepository estudianteRepository;

   

    public ServicioInscripcion(CursoRepository cursoRepository, EstudianteRepository estudianteRepository) {

        this.cursoRepository = cursoRepository;

        this.estudianteRepository = estudianteRepository;

    }

   

    public Inscripcion inscribirEstudianteEnCurso(CursoId cursoId, EstudianteId estudianteId, Precio precio) {

        Curso curso = cursoRepository.buscarPorId(cursoId)

            .orElseThrow(() -> new RecursoNoEncontradoExcepcion("Curso no encontrado"));

           

        Estudiante estudiante = estudianteRepository.buscarPorId(estudianteId)

            .orElseThrow(() -> new RecursoNoEncontradoExcepcion("Estudiante no encontrado"));

           

        Inscripcion inscripcion = curso.inscribirEstudiante(estudiante, precio);

       

        cursoRepository.guardar(curso);

       

        return inscripcion;

    }

}

 

// -- IMPLEMENTACIÓN DEL REPOSITORIO (INFRAESTRUCTURA) --

 

public class CursoRepositoryImpl implements CursoRepository {

    private final EntityManager entityManager;

   

    public CursoRepositoryImpl(EntityManager entityManager) {

        this.entityManager = entityManager;

    }

   

    @Override

    public void guardar(Curso curso) {

        entityManager.persist(curso);

    }

   

    @Override

    public Optional<Curso> buscarPorId(CursoId id) {

        Curso curso = entityManager.find(Curso.class, id);

        return Optional.ofNullable(curso);

    }

   

    @Override

    public List<Curso> buscarPorTitulo(String titulo) {

        return entityManager.createQuery(

            "SELECT c FROM Curso c WHERE LOWER(c.titulo) LIKE :titulo", Curso.class)

            .setParameter("titulo", "%" + titulo.toLowerCase() + "%")

            .getResultList();

    }

}

 

// -- CAPA DE APLICACIÓN (CASOS DE USO) --

 

public class InscribirEstudianteUseCase {

    private final ServicioInscripcion servicioInscripcion;

   

    public InscribirEstudianteUseCase(ServicioInscripcion servicioInscripcion) {

        this.servicioInscripcion = servicioInscripcion;

    }

   

    public InscripcionDTO ejecutar(InscripcionCommand command) {

        CursoId cursoId = new CursoId(command.getCursoId());

        EstudianteId estudianteId = new EstudianteId(command.getEstudianteId());

        Precio precio = new Precio(command.getMonto(), command.getMoneda());

       

        Inscripcion inscripcion = servicioInscripcion.inscribirEstudianteEnCurso(

            cursoId, estudianteId, precio);

           

        return new InscripcionDTO(

            inscripcion.getId().getValor(),

            inscripcion.getCursoId().getValor(),

            inscripcion.getEstudianteId().getValor(),

            inscripcion.getFechaInscripcion(),

            inscripcion.getEstado().toString(),

            inscripcion.getPrecio().getMonto(),

            inscripcion.getPrecio().getMoneda()

        );

    }

}

 

// -- CONTROLADOR (ADAPTADOR PRIMARIO) --

 

@RestController

@RequestMapping("/api/inscripciones")

public class InscripcionController {

    private final InscribirEstudianteUseCase inscribirEstudianteUseCase;

   

    public InscripcionController(InscribirEstudianteUseCase inscribirEstudianteUseCase) {

        this.inscribirEstudianteUseCase = inscribirEstudianteUseCase;

    }

   

    @PostMapping

    public ResponseEntity<InscripcionDTO> inscribirEstudiante(@RequestBody InscripcionCommand command) {

        try {

            InscripcionDTO inscripcionDTO = inscribirEstudianteUseCase.ejecutar(command);

            return ResponseEntity.status(HttpStatus.CREATED).body(inscripcionDTO);

        } catch (RecursoNoEncontradoExcepcion e) {

            return ResponseEntity.notFound().build();

        } catch (DominioExcepcion e) {

            return ResponseEntity.badRequest().build();

        }

    }

}

En Resumen

Jorge Mercado

#JMCoach

Il Potere Silenzioso del Design di prodotti tecnologici

 

Il Potere Silenzioso del Design: Come UX, UI e CX Trasformano l'Esperienza Digitale

Nell'era digitale, la differenza tra un prodotto che fallisce e uno che conquista milioni di utenti spesso si nasconde in dettagli apparentemente insignificanti: un pulsante posizionato nel punto giusto, un'interfaccia che anticipa i bisogni dell'utente, un'esperienza che fluisce naturalmente come una conversazione tra amici.


L'Ecosistema dell'Esperienza Utente: Oltre le Apparenze

Quando parliamo di User Experience (UX), User Interface (UI) e Customer Experience (CX), non stiamo semplicemente discutendo di estetica o funzionalità isolate. Stiamo esplorando un ecosistema complesso dove ogni elemento lavora in sinergia per creare quello che gli esperti chiamano "design invisibile" - quello che funziona così bene che l'utente non si accorge nemmeno di utilizzarlo.

UX: L'Architettura dell'Esperienza

La User Experience è come l'architettura di una casa: definisce come le persone si muovono attraverso gli spazi digitali, dove trovano quello che cercano, e come si sentono durante il percorso. Un'ottima UX è come un maggiordomo discreto che anticipa ogni esigenza senza mai farsi notare.

UI: Il Linguaggio Visivo

L'User Interface è la conversazione visiva tra il prodotto e l'utente. Ogni colore, ogni tipografia, ogni micro-animazione comunica qualcosa. È il ponte tra l'intenzione dell'utente e l'azione del sistema.

CX: L'Ecosistema Completo

La Customer Experience abbraccia l'intero viaggio del cliente, dal primo touchpoint fino al supporto post-vendita. È l'orchestrazione di tutti i punti di contatto per creare un'esperienza coerente e memorabile.


Design Thinking: Il Processo che Cambia le Regole del Gioco

Il Design Thinking non è solo una metodologia, è una filosofia che mette l'essere umano al centro di ogni decisione progettuale. Il processo si articola in cinque fasi fondamentali:

Empatizzare: Comprendere profondamente gli utenti, i loro dolori, le loro aspirazioni. Non si tratta di quello che pensiamo abbiano bisogno, ma di quello di cui realmente hanno bisogno.

Definire: Sintetizzare le osservazioni in una sfida di design chiara e azionabile. Il problema ben definito è già metà della soluzione.

Ideare: Generare una vasta gamma di soluzioni creative. Qui la quantità genera qualità, e nessuna idea è troppo audace.

Prototipare: Trasformare le idee in artefatti tangibili che possono essere testati e migliorati rapidamente.

Testare: Validare le soluzioni con utenti reali, imparando dai fallimenti per iterare verso l'eccellenza.

Il Product Operating Model: L'Orchestrazione dell'Innovazione

Il Product Operating Model rappresenta la struttura organizzativa e i processi che permettono ai team di creare prodotti eccezionali in modo consistente. È l'architettura invisibile che supporta l'innovazione continua.

I Pilastri del Modello

Team Cross-Funzionali: Designer, sviluppatori, product manager e stakeholder lavorano insieme come un'orchestra, dove ogni strumento contribuisce all'armonia finale.

Iterazione Continua: Il miglioramento non è un evento, è un processo. Ogni release è un'opportunità per imparare e crescere.

Data-Driven Decision Making: Le decisioni si basano su dati concreti e feedback degli utenti, non su opinioni o intuizioni isolate.

Caso di Studio: La Trasformazione di "VitalFit" - App per il Wellness Personale

Per illustrare questi concetti, esploriamo la trasformazione di VitalFit, un'applicazione mobile per il fitness e il benessere che ha rivoluzionato l'esperienza utente attraverso un approccio integrato di UX/UI/CX.

La Sfida Iniziale

VitalFit aveva un problema comune: un'app funzionale ma con un tasso di abbandono del 78% entro la prima settimana. Gli utenti scaricavano l'app con entusiasmo ma la abbandonavano rapidamente, frustrati dalla complessità e dalla mancanza di personalizzazione.

L'Approccio Design Thinking

Fase di Empatizzazione: Il team ha condotto 50 interviste approfondite con utenti di diverse età e livelli di fitness. Ha emergono insight cruciali: le persone non volevano un'altra app complessa, ma un compagno digitale che li motivasse senza giudicarli.

Definizione del Problema: "Come possiamo creare un'esperienza di wellness che si adatti naturalmente alla vita quotidiana delle persone, motivandole senza sopraffarle?"

Ideazione: Il team ha generato oltre 200 idee, dalle più pratiche alle più visionarie. L'idea vincente è stata quella di un "AI Coach Emotivo" che apprende dalle abitudini dell'utente.


La Soluzione: Design Centrato sull'Emozione

Onboarding Rivoluzionario

Invece del classico questionario noioso, VitalFit ha creato un onboarding conversazionale che si sente come chattare con un amico esperto di fitness. L'interfaccia usa colori caldi e microanimazioni che celebrano ogni piccolo passo.

Dashboard Intelligente

La schermata principale non bombarda l'utente con statistiche, ma presenta tre elementi essenziali:

  • Un messaggio personalizzato motivazionale
  • L'obiettivo del giorno (sempre raggiungibile)
  • Una visualizzazione del progresso che celebra i successi

Micro-Interazioni che Deliziano

Ogni tap, swipe e completamento di attività è accompagnato da feedback visivi e aptici che rendono l'interazione gratificante. Non è solo funzionale, è emotivamente ricompensante.

I Risultati Straordinari

Dopo sei mesi dal rilancio:

  • Tasso di ritenzione a 7 giorni: dall'22% al 67%
  • Sessioni giornaliere medie: +340%
  • Rating App Store: da 2.1 a 4.7 stelle
  • NPS (Net Promoter Score): da -12 a +58

I Principi del Design Potente e Semplice

1. Legge di Jakob: La Familiarità è Potere

Gli utenti trascorrono la maggior parte del loro tempo su altri siti e app. Il tuo design dovrebbe sfruttare i pattern mentali esistenti, non combatterli.

2. Principio di Fitts: La Fisica dell'Interazione

La dimensione e la distanza degli elementi interattivi influenzano direttamente l'usabilità. I pulsanti principali devono essere grandi e facilmente raggiungibili.

3. Regola del 7±2: Il Limite Cognitivo

La mente umana può elaborare efficacemente circa 7 elementi contemporaneamente. Design che rispettano questo limite riducono lo stress cognitivo.

4. Principio di Closure: Il Cervello Completa i Pattern

Le persone tendono a completare mentalmente forme e pattern incompleti. Un design elegante sfrutta questa tendenza per creare interfacce pulite e intuitive.

L'Impatto Emotivo del Buon Design

Il design eccezionale non si limita a funzionare bene, genera emozioni positive. Quando un utente interagisce con un'interfaccia ben progettata, il suo cervello rilascia piccole quantità di dopamina - lo stesso neurotrasmettitore associato al piacere e alla ricompensa.

Micro-Momenti di Gioia

Sono i piccoli dettagli che fanno la differenza: un'animazione fluida, un messaggio di conferma incoraggiante, un caricamento che intrattiene invece di frustrare. Questi "micro-momenti di gioia" trasformano task funzionali in esperienze memorabili.

Design Emotivo: I Tre Livelli di Norman

Livello Viscerale: La prima impressione, l'attrazione immediata Livello Comportamentale: L'usabilità, l'efficienza, la facilità d'uso
Livello Riflessivo: L'identificazione personale, il senso di appartenenza

Il Futuro del Design: Verso l'Esperienza Predittiva

Stiamo entrando nell'era del design predittivo, dove l'intelligenza artificiale e l'apprendimento automatico permettono alle interfacce di anticipare i bisogni degli utenti prima ancora che se ne rendano conto.

Design Adattivo e Contestuale

Le interfacce del futuro si adatteranno automaticamente al contesto: la stessa app avrà comportamenti diversi se utilizzata di mattina, durante una pausa pranzo, o la sera. Non solo responsive nel layout, ma responsive nell'esperienza.

Interfacce Conversazionali Evolute

I chatbot stanno evolvendo verso assistenti digitali che comprendono il linguaggio naturale, le emozioni e il contesto. Il design si sposta dai pulsanti alle conversazioni.

Metriche che Contano: Misurare l'Impatto del Design

Metriche Quantitative

  • Time on Task: Quanto tempo impiegano gli utenti per completare azioni chiave
  • Success Rate: Percentuale di utenti che completano con successo i loro obiettivi
  • Error Rate: Frequenza degli errori e abbandoni
  • Return Usage: Frequenza con cui gli utenti tornano all'applicazione

Metriche Qualitative

  • System Usability Scale (SUS): Valutazione standardizzata dell'usabilità
  • Net Promoter Score (NPS): Propensione degli utenti a raccomandare il prodotto
  • Customer Effort Score (CES): Quanto sforzo percepito richiede l'uso del prodotto
  • Sentiment Analysis: Analisi delle emozioni nei feedback degli utenti


Conclusione: Il Design come Vantaggio Competitivo Sostenibile

In un mondo dove la tecnologia si commoditizza rapidamente, il design rimane l'ultimo vantaggio competitivo sostenibile. Non si tratta solo di rendere le cose belle, ma di creare esperienze che risuonano emotivamente con le persone, che risolvono problemi reali e che generano valore tangibile.

Il design eccezionale è silenzioso ma potente. È quella forza invisibile che fa sì che un utente scelga la tua app tra mille alternative, che torni ogni giorno con piacere, e che la raccomandi spontaneamente agli amici.

Quando UX, UI e CX lavorano in armonia, supportate da un solido Design Thinking e un Product Operating Model efficace, non stiamo semplicemente creando prodotti digitali. Stiamo progettando il futuro delle interazioni umane con la tecnologia.

La domanda non è se il buon design fa la differenza, ma se possiamo permetterci di ignorarlo. In un'economia dell'attenzione dove ogni secondo conta, il design non è più un lusso - è una necessità strategica per chiunque voglia costruire prodotti che non solo funzionano, ma che vengono amati.

 

Jorge Mercado

#JMCoach


Cuando no se resuelve la operación.

  El impacto del presupuesto cloud. Cuando no se resuelve la operación. El peor es que los sistemas que pagas de más no están resolviendo na...