ES | My Movies checklist - Front & Backend con DDD y CQRS
http://localhost:4000/2022/12/01/movies-checklist-1/basic-pages.png
La idea de este post es hacer un proyecto completo desde cero. Este es el primero de 24 apartados y espero que este proyecto te llame la atención, cualquier comentario no dudes en contactarme @whistler092
Descripcion general y frontend
Disclamer: No soy un experto en react ni mucho menos. La idea es encapsular mi proceso de creación de un proyecto para los amigos que leen este post :D
El proyecto se llama My Movies Checklist y consiste en un software donde se puedan ver todas las películas del mercado, poder llevar un seguimiento de las películas que ya te vistes y las que te quieres ver.
También poder compartir tus opiniones con el resto de mundo y tener tu propio ranking de películas personales.

La idea es tener 3 paginas básicas:
Películas: En esta página estarán todas las películas del cine, cada tarjeta tendrá el poster, el nombre de la película, genero, fecha y una corta sinopsis. En esa carta habrán 2 botones, el primero es para marcar que la película ya te la viste, te pedirá un puntaje y agregar unos comentarios. En la segunda es para agregar esa película en una lista de películas por ver.

Comentarios: En esta página se verán todos los comentarios de todos los usuarios a las películas que se han visto.
Tu perfil: En esta página se pueden ver todas las películas que te has visto y las películas que están pendientes por ver.
La integración se hará con el web service themoviedb.org.
Vamos a ir saltando del back-end al front-end dependiendo de lo que necesitemos, así que vamos a iniciar con el proyecto react:
1 |
|
Ya tenemos nuestro proyecto react creado, así que agreguemos el enrutador con react router. Aquí un tutorial de la página oficial para aprender unos buenos fundamentos https://reactrouter.com/en/main/start/tutorial
1 |
|
Yo aprendí react antes de React Hooks pero no he tenido la oportunidad de seguir trabajando en él; para mi react ha cambiado un montón, ando en proceso de reaprender y me disculparan si cometo algún error. El enrutador de react también ha cambiado mucho así que básicamente sentí que aprendí nuevamente react con esto. ¡Así que vamos a darle!
Las primeras rutas que necesitamos aquí son las de:
- Peliculas: /movies
- Comentarios: /comments
- Mi perfil: /profile
Vamos a crearlas:
Creamos un nuevo folder llamado routes donde ubicaremos todas las páginas. Por lo pronto nuestro main.jsx va a lucir asi:
1 | import React from "react"; |
Hemos cambiado el objeto <App /> que se retorna en el createRoot y ahora vamos a retornar un objeto del react-router-dom. El <RouterProvider> nos permite que todos los objetos que se le pasen, van a estar bajo el dominio del enrutador. Por tal razón, vamos a pasarle el objeto router definido arriba.
En este objeto hay que crear 4 nuevas rutas, el root / que contendrá todo el menú. En este componente <Root /> que por el momento va a ser algo dummy tendrá el objeto <Outlet /> donde se hará render de los children definidos anteriormente.
1 | import { Outlet } from "react-router"; |
Para los children, vamos a tener 3 rutas, el /movies, /comments y /profile.
Vamos a crear estos componentes también vacíos con tal de tener un acercamiento básico de las páginas que tendremos.
1 | export default function Profile() { |
Hasta el momento, hemos creado el siguiente workflow

Aquí podemos chequear el código en mi repositorio
En el próximo post vamos a armar un menú básico para poder navegar por las diferentes rutas y vamos a cargar una información dummy para poder interactuar.
Enrutamiento, info dummy y css
En el apartado anterior, agregamos el enrutamiento de react con las paginas que necesitamos: /Movies, /Comments y /Profile. La meta de este post es armar un menú básico para poder navegar por las diferentes rutas y vamos a cargar una información dummy para poder interactuar.
Hay varias formas de armar un menú, una de ellas es con un framework frontend como React Bootstrap y tiene un menú bastante interesante, si les interesa, échenle un ojo. En este caso, vamos a intentar evitar agregar Bootstrap y hacerlo manual para practicar un poco.
Así que … primero que todo, quiero presentarles Sass: Syntactically Awesome Style Sheets, Sass es un preprocesador de CSS que nos ayudara a darle vitaminas al CSS. Es muy fácil de usar, porque la sintaxis es la misma que la de CSS, solo que permite anidación. Ya vamos a verlo!
Para instalarlo, vamos a ir a la consola y escribir npm install sass y ya. React automáticamente detectara los archivos con extensión .scss.
Uno de los cambios que vamos a iniciar es modificar el root.jsx para agregar nuestro nuevo menú.
1 | import { Outlet } from "react-router"; |
Aquí vamos a hacer uso del NavLink que nos permitirá hacer un redireccionamiento con el react-router-dom.
Creemos un nuevo archivo llamado /src/routes/root.scss. Importado en el archivo anterior, vamos a agregarle un poquito de estilo a este menú:
1 | #header { |
Una de las cosas mas interesantes de usar scss es los componentes anidados. Como pueden ver, dentro del id header, todos los componentes ul serán afectados con el CSS adentro.
Otra de las cosas que me gusta usar es flexbox. Les recomiendo un tutorial bastante interesante de los magos de css-tricks.com. El valor por defecto de flex es organizar los componentes de forma horizontal, lo cual es lo que necesitamos. Adicionalmente, ajuste unos colores del src/index.css, importé un font llamado Montserrat y ajuste el body con flexbox también.
La forma que uso para importar un nuevo font es usando la etiqueta import. Si quieres mirar más fonts, chequea esta página
1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap"); |

Aquí un commit hasta este punto
Ahora lo que vamos a hacer es mostrar un par de películas de forma fake antes de crear el backend y registrarnos en themoviedb.org, todo esto para saber exactamente que propiedades necesitamos mostrar en la UI. Así que descarguemos una json previamente guardad de del API de películas populares de la siguiente URL y guardemos ese json en el path /public/db.json.
Hay varias formas de hacer esto, vamos a hacer una petición HTTP para obtener este archivo de nuestra carpeta pública, y así para poder reutilizar parte del código cuando llegue muestro backend, vamos a instalar axios usando npm install axios.
Abramos nustro archivo src/routes/movies.jsx:
1 | import axios from "axios"; |
Vamos a usar react hooks, esto nos permite usar estados y otras características de react sin tener que escribir clases. useState nos ayuda para mantener estados dentro de la función. Aquí puedes leer documentación al respecto si no estas familiarizado.
Si tuviéramos que escribir el anterior código de useState con clases, sería algo como lo siguiente:
1 | class Movies extends React.Component { |
También vamos a usar useEffect, esto nos permite realizar efectos secundarios en un componente. Podemos realizar cosas como obtener data de un API, subscribirse o manualmente cambiar el DOM. En nuestro caso, vamos a usarlo para invocar axios y traer el json del path público, finalmente en el .then, vamos a guardar la información en el state. Así es como podremos obtener el resultado siguiente resultado en consola:
1 | movies [] |
Ahora hagamos render de los títulos:
1 | export default function Movies() { |
Vamos a agregar un poco de estilo a esta página, por tal razón, vamos a crear un nuevo archivo src/routes/movies.scss y organicemos un poco las películas:
1 | .films { |
Vamos a organizar todas las películas en la clase films y usaremos flexbox con flex-direction: row; para acomodar todas las películas de forma horizontal y complementamos con flex-wrap: wrap para que salten de línea cuando se llene cada row.
Dentro de cada película, vamos a ajustar un poco cada uno, dándole un tamaño fijo mínimo y máximo. Tambien usaremos flexbox para que todo sea de tipo columna.
Ahora las películas podrían lucir de la siguiente forma:

Aquí un commit con los últimos cambios
Diagrama de base de datos
En el apartado anterior, mejoramos un poco la ruta de movies mostrando las últimas películas desde una data moq, la idea de este post es crear nuestro backend usando .net Core, EF y PostgreSQL.
Antes de arrancar a hacer un diagrama de base de datos, necesitamos validar algunos requerimientos.
- Como usuario, quiero poder buscar cualquier película dentro del catálogo, cada película debe tener un identificador único. Se debe persistir la película en la base de datos con información básica.
- Como usuario, quiero poder seleccionar una película y poder marcar cualquier película para ver.
- Como usuario, quiero poder seleccionar una película y poder marcarla como vista. También poder agregar un comentario y una puntuación a las películas que el usuario haya visto.
- Quiero poder listar mis películas pendientes a verme y las películas vistas.
Ahora sí, con esto podríamos definir la estructura de la base de datos. He creado un diagrama usando dbdiagram.io para visualizar las relaciones:

La idea es crear un usuario users, que es un usuario registrado desde el proveedor de autenticación de Auth0. Un usuario podrá tener varias películas asociadas users_movies y hay dos tipos de asociaciones: Las películas por ver to_watch y las películas vistas saw_it. Cuando se asocia una película vista, este puede tener uno o más comentarios comments.
Vamos a crear las tablas en la base de datos.
1 |
|
Haciendo la prueba de concepto, podemos tener los siguientes query’s. Arranquemos por llenar información básica
1 |
|
Ahora asociemos el usuario con una película de modo para ver
1 |
|
Ahora asociemos un comentario con una asociación de una película
1 | -- Add new comment into a movie |
En caso de que sea necesario, un script para borrar todo
1 |
|
DDD y creacion del proyecto backend
En el apartado anterior, estuvimos creando lo que seria una idea inicial de la base de datos, ahora vamos a iniciar creando nuestra API. Este post estoy tratando de plasmar mi opinión personal en este momento, la cual puede cambiar 😅😅.
Nota 2: Puede que algunos conceptos sean avanzados para el proyecto que intento hacer, pero intentare explicarlos lo mejor que pueda, así que si sientes que algo no sea suficientemente claro, no dudes en escribirme y podemos mejorar el post. Gracias :D
El acercamiento que vamos a estar usando en este proyecto es el de Clean Architecture y Domain Driven Design. Vamos a iniciar dando una breve explicación de que es Domain-Driven Design aka DDD y porque me parece importante integrarlo en mis proyectos. ¿Entonces, en que consiste DDD y Arquitectura Limpia? Bueno, esto es una serie de principios y prácticas que nos permitirán modelar nuestro software para ser mantenible y mucho más escalables. Conceptos como Arquitectura Limpia, Arquitectura Cebolla o Arquitectura Hexagonal comparten la misma meta: crear sistemas robustos y desacoplados. Veamos un diagrama de cómo podríamos estructurar nuestra aplicación:

Con este acercamiento, (Como lo describe Jason Taylor en este gran video) el Domain y el Application son las unidades centrales de la aplicación, esto es conocido como el Core del sistema.
- Domain: Contiene la lógica de negocio universal (entidades, objetos de valor, lógica pura que no cambia entre aplicaciones). No depende de nada.
- Application: Contiene la lógica específica de la aplicación (casos de uso, comandos, queries). Depende solo del Dominio.
Las capas externas, Presentation (la API) e Infrastructure (la base de datos, servicios externos), dependen del Core, pero no al revés. Esta inversión de dependencias se logra usando interfaces en la capa de Application, que son implementadas en la capa de Infrastructure. Así, si mañana queremos cambiar de PostgreSQL a SQL Server, solo cambiamos la implementación en Infrastructure sin tocar la lógica de negocio.
Algunas características de Clean Architecture:
- Independiente del Framework o lenguaje de programación.
- Testeable: La lógica de negocio no conoce la base de datos ni la UI, por lo que es fácil de probar.
- Independiente de la UI y de la base de datos.
- Independiente de factores externos.
Yendo un poquito más al detalle de los tipos de cosas que vamos a ver en cada capa son:
- Domain: Entidades, Objetos de Valor, Lógica de negocio pura.
- Application: Interfaces (ej.
IMovieRepository), Lógica de aplicación, Comandos/Queries, Validaciones. - Infrastructure: Implementación de persistencia (Entity Framework), Identidad (Auth0), envío de emails, etc.
- Presentation: Controladores de Web API, páginas MVC, o en nuestro caso, el frontend en React que consumirá esta API.
Listo, entonces vamos a iniciar creando el proyecto desde cero. Para ello tenemos que tener el SDK de .Net Core instalado.
Para iniciar, abrimos la terminal y ejecutamos los siguientes comandos para crear la solución:
1 | PS C:\...> cd .\movies-api\ |
Vamos a crear la capa de presentación (Movies.Api y Movies.Contracts) y las capas del Core e Infraestructura:
1 | # Presentation |
Ya que tenemos todos los proyectos creados, agreguemos las dependencias entre ellos de acuerdo al siguiente diagrama:

1 | # Presentation depends on Application and Contracts |
NOTA: Aunque conceptualmente Presentation no debería depender de Infrastructure, agregamos la referencia para que el contenedor de Inyección de Dependencias en Movies.Api pueda registrar los servicios definidos en Infrastructure. No usaremos clases de Infrastructure directamente en la API.
1 | PS C:\...\Movies> dotnet build |
Corremos el backend para verificar que todo funciona:
1 | PS C:\...\Movies> dotnet run --project .\Movies.Api\ |

Entidades y Configuración de CQRS
En el apartado anterior, creamos la estructura de la solución .NET. Ahora, vamos a definir nuestras entidades de dominio y a configurar la aplicación para usar el patrón CQRS con MediatR.
Primero, limpiamos el código de ejemplo de la plantilla:
1 | PS C:\...\Movies> rm .\Movies.Api\Controllers\WeatherForecastController.cs |
Creación de Entidades de Dominio (Enfoque DDD)
En el proyecto Movies.Domain, crearemos clases que representen nuestras tablas. Pero en lugar de crear simples contenedores de datos (POCOs), aplicaremos principios de DDD para crear un modelo de dominio rico y robusto.
1. Entidad Base
Primero, una pequeña abstracción. Todas nuestras entidades tendrán un Id. Podemos crear una clase base para manejar esto y la comparación de igualdad, que en DDD se basa en la identidad.
Movies.Domain/Common/Entity.cs
1 | namespace Movies.Domain.Common; |
2. Entidad Movie
La entidad Movie representa una película de nuestro catálogo. Su información proviene principalmente de una fuente externa (TheMovieDB), por lo que su comportamiento es limitado, pero su creación debe ser controlada.
Movies.Domain/Entities/Movie.cs
1 | using Movies.Domain.Common; |
¿Por qué este enfoque?
- Encapsulación: Los
settersson privados. El estado de la entidad no puede ser modificado arbitrariamente desde el exterior. - Invariantes: El método
Createse asegura de que una película siempre se cree con los datos necesarios (un título, un ID de TMDB válido, etc.). Protege la integridad del objeto.
3. Entidad User (Aggregate Root)
El User es un Aggregate Root. Es la entidad principal de un grupo de objetos relacionados (el “agregado”). En nuestro caso, un User es dueño de su lista de UserMovie y sus Comment. Toda la interacción con estas entidades hijas debe pasar a través del User.
Movies.Domain/Entities/User.cs
1 | using Movies.Domain.Common; |
4. Entidad UserMovie
Esta es una entidad hija dentro del agregado User. No tiene sentido que exista sin un usuario. Su ciclo de vida es gestionado por el User.
Movies.Domain/Entities/UserMovie.cs
1 | using Movies.Domain.Common; |
5. Entidad Comment
Es la última pieza de nuestro agregado. Vive dentro de un UserMovie y su creación es controlada por este.
Movies.Domain/Entities/Comment.cs
1 | using Movies.Domain.Common; |
¿Qué hemos ganado con este enfoque?
- Claridad: El código expresa claramente las reglas de negocio (ej.
MarkAsSeen,AddComment). - Robustez: Es mucho más difícil poner el sistema en un estado inválido. No puedes crear un comentario para una película que no has visto.
- Mantenibilidad: Si una regla de negocio cambia (ej. el puntaje ahora es de 1 a 5), el cambio se hace en un solo lugar: la entidad
Comment. - Cohesión: La lógica que pertenece a una entidad está dentro de esa entidad.
Este es el corazón de un diseño guiado por el dominio. Ahora, la capa de Aplicación usará estas entidades para orquestar los casos de uso, sabiendo que el dominio se protegerá a sí mismo.
Configuración de CQRS con MediatR
CQRS (Command Query Responsibility Segregation) es un patrón que separa las operaciones que leen datos (Queries) de las que modifican datos (Commands). Esto simplifica cada lado.
MediatR es una librería que implementa el patrón Mediator. Nos ayuda a desacoplar el emisor de una solicitud (ej. un controlador API) de su manejador (la lógica de negocio en la capa de Aplicación). Es perfecto para implementar CQRS.
Instalemos los paquetes necesarios en el proyecto Movies.Application:
1 | PS C:\...\Movies> dotnet add .\Movies.Application\ package MediatR |
Ahora, registramos MediatR en el contenedor de dependencias. Abre Program.cs en Movies.Api y agrega la siguiente línea:
Movies.Api/Program.cs
1 | // ... |
Para que MediatR encuentre los manejadores, necesita saber en qué ensamblado (proyecto) buscar. Creamos una interfaz vacía en Movies.Application que sirva como marcador.
Movies.Application/IAssemblyMarker.cs
1 | namespace Movies.Application; |
Y actualizamos Program.cs:
1 | builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Movies.Application.IAssemblyMarker).Assembly)); |
Configuración de Persistencia con Entity Framework Core
Ahora, conectemos nuestra aplicación a la base de datos PostgreSQL usando EF Core, un ORM (Object-Relational Mapper) que nos permite trabajar con la base de datos usando objetos C#.
Instalemos los paquetes de EF Core en el proyecto Movies.Infrastructure:
1 | PS C:\...\Movies> dotnet add .\Movies.Infrastructure\ package Microsoft.EntityFrameworkCore |
Y la herramienta de migraciones en Movies.Api para poder ejecutar comandos dotnet ef:
1 | PS C:\...\Movies> dotnet add .\Movies.Api\ package Microsoft.EntityFrameworkCore.Design |
A continuación, creamos nuestro DbContext en Movies.Infrastructure. Esta clase representa una sesión con la base de datos.
Movies.Infrastructure/Persistence/MoviesDbContext.cs
1 | using Microsoft.EntityFrameworkCore; |
Finalmente, registramos el DbContext en Program.cs y le decimos que use PostgreSQL, obteniendo la cadena de conexión desde la configuración (appsettings.json).
Movies.Api/Program.cs
1 | // ... |
Movies.Api/appsettings.json
1 | { |
¡Asegúrate de cambiar los datos de tu conexión!
Implementación de un Flujo End-to-End (Query)
Con todo configurado, vamos a implementar nuestro primer caso de uso: obtener todas las películas.
Definir la Interfaz del Repositorio (
Application): La aplicación define qué necesita, no cómo se obtiene.Movies.Application/Interfaces/IMovieRepository.cs1
2
3
4
5
6
7using Movies.Domain.Entities;
namespace Movies.Application.Interfaces;
public interface IMovieRepository
{
Task<IEnumerable<Movie>> GetAllAsync(CancellationToken cancellationToken);
}Implementar el Repositorio (
Infrastructure): La infraestructura provee la implementación concreta.Movies.Infrastructure/Repositories/MovieRepository.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21using Microsoft.EntityFrameworkCore;
using Movies.Application.Interfaces;
using Movies.Domain.Entities;
using Movies.Infrastructure.Persistence;
namespace Movies.Infrastructure.Repositories;
public class MovieRepository : IMovieRepository
{
private readonly MoviesDbContext _context;
public MovieRepository(MoviesDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Movie>> GetAllAsync(CancellationToken cancellationToken)
{
return await _context.Movies.AsNoTracking().ToListAsync(cancellationToken);
}
}Y lo registramos en
Program.cs:1
2
3// ...
builder.Services.AddScoped<IMovieRepository, MovieRepository>();
// ...Definir el Query y su Handler (
Application):Movies.Application/Features/Movies/Queries/GetMoviesQuery.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24using MediatR;
using Movies.Application.Interfaces;
using Movies.Domain.Entities;
namespace Movies.Application.Features.Movies.Queries;
// El Query: no lleva datos, solo pide la lista.
public record GetMoviesQuery() : IRequest<IEnumerable<Movie>>;
// El Handler: contiene la lógica para resolver el query.
public class GetMoviesQueryHandler : IRequestHandler<GetMoviesQuery, IEnumerable<Movie>>
{
private readonly IMovieRepository _movieRepository;
public GetMoviesQueryHandler(IMovieRepository movieRepository)
{
_movieRepository = movieRepository;
}
public async Task<IEnumerable<Movie>> Handle(GetMoviesQuery request, CancellationToken cancellationToken)
{
return await _movieRepository.GetAllAsync(cancellationToken);
}
}Crear el Endpoint en la API (
Presentation):Movies.Api/Controllers/MoviesController.cs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22using MediatR;
using Microsoft.AspNetCore.Mvc;
using Movies.Application.Features.Movies.Queries;
[]
[]
public class MoviesController : ControllerBase
{
private readonly ISender _mediator;
public MoviesController(ISender mediator)
{
_mediator = mediator;
}
[]
public async Task<IActionResult> GetMovies(CancellationToken cancellationToken)
{
var movies = await _mediator.Send(new GetMoviesQuery(), cancellationToken);
return Ok(movies);
}
}El controlador es muy simple. Su única responsabilidad es recibir la petición HTTP, enviarla a MediatR y devolver el resultado. Toda la lógica de negocio está encapsulada y desacoplada.
Conclusión y Próximos Pasos
Si has llegado hasta aqui, hemos construido una base sólida para nuestro backend usando principios de Clean Architecture, DDD y CQRS.
En este post logramos:
- Diseñar la base de datos y la arquitectura de la solución.
- Configurar un proyecto .NET 7 siguiendo las mejores prácticas.
- Implementar el patrón CQRS con MediatR para un flujo de datos limpio.
- Conectar la aplicación a una base de datos PostgreSQL con EF Core.
- Crear un flujo completo de extremo a extremo para consultar datos.
El proyecto está lejos de terminar, pero ahora tenemos una estructura escalable y mantenible. En los próximos posts, podríamos explorar:
- Implementar Commands: Crear, actualizar y marcar películas como vistas o por ver.
- Validación: Agregar validaciones a nuestros comandos usando librerías como FluentValidation.
- Autenticación y Autorización: Integrar Auth0 para proteger nuestros endpoints.
- Conectar el Frontend: Finalmente, hacer que nuestra aplicación React consuma esta nueva API.
Espero que este recorrido te haya sido útil.