This page looks best with JavaScript enabled

Algunas pinceladas sobre como arquitecturar tu WEB/API

 ·  ☕ 29 min  ·  ✍️ eiximenis

Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

El otro día Antíoco Llanos lanzaba el siguiente tweet:

(Siempre las mismas dudas. Que dependa mi capa de negocio de EF para usar sus IDbSet o no… ¿abstraer la abstracción?)

Contesté yo con algunas sugerencias y eso derivó en otra conversación paralela, así que me parece una buena idea poner algunas pinceladas sobre como podemos abordar ese aspecto. Por supuesto y como digo siempe: no hay balas de plata y no existe la arquitectura para todo. Cada proyecto debe analizarse para valorar la arquitectura a abordar, o arquitecturas porque se pueden usar distintas en un mismo proyecto. Así, este post no tiene más pretensión que contarte algunas ideas, pero las conclusiones que saques de ellas son cosa tuya 😉

Nota: Todos los ejemplos de código estarán en netcore y usando EF Core como ORM. Pero vamos, muchas de las cosas que se cuenten (por no decir todo) son aplicables a MVC5 con EF 6.x o NHibernate. No te quedes con que eso “solo sirve para netcore”, pero algún framework hay que usar para el código 😛

1. Usar el contexto directamente en los controladores

Antíoco se preguntaba si abstraer la abstracción. Personalmente soy enemigo de abstraer abstracciones porque toda abstracción fuga así que la abstracción de una abstracción o bien fuga doblemente o bien fuga abstractamente y ninguna de las dos cosas parece muy buena.

Si el proyecto es un proyecto muy CRUD, con operaciones CRUD simples, porqué no inyectar el contexto de EF en el controlador directamente?

    [Route("api/[controller]")]
    public class BeersController : Controller
    {
        private readonly BeersDbContext _db;
        public BeersController(BeersDbContext ctx) => _db = ctx;

        // GET api/values
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var beers = await _db.Beers.ToListAsync();
            return Ok(beers);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var beer = await _db.Beers.SingleOrDefaultAsync(b => b.Id == id);
            return beer != null ? (IActionResult)Ok(beer) : (IActionResult)NotFound();
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody]Beer newBeer)
       {
            if (string.IsNullOrWhiteSpace(newBeer.Name))
            {
                return BadRequest();
            }
            _db.Beers.Add(newBeer);
            await _db.SaveChangesAsync();
            return NoContent();
        }
    }
}

Hay varios argumentos que se esgrimen en contra de esa aproximación:

  1. El controlador está a atado a EF. ¿Qué ocurre si algún día quiero irme de EF? Efectivamente, el controlador está atado a EF… y también a MVC Core. No sé si me explico: Si te planteas el abstraerte de EF solo “por si en un futuro quiero cambiarlo por otro framework”, también deberías plantearte abstraerte de MVC Core, no vayas a querer usar NancyFx en un futuro. Hazte un favor y sigue el principio YAGNI.
    • Eso no es testeable. Para probar eso necesitas tener EF sí. Pero veamos que probamos en esos casos tan CRUD. No hay casi lógica, en todo caso la poca lógica que hay es la de las validaciones (p. ej. en el método Post validamos que el nombre no esté vacío). Eso es lo primero que deberías separar del controlador. Puedes mover esas validaciones fuera (directamente en la clase Beer a través de IValidatableObject o usando FluentValidation o similar. Eso te permite probar las validaciones mediante pruebas unitarias de forma sencilla. ¿No te convence solo probar las validaciones? Claro, quieres probar las consultas y los inserts…

1.1 Intentando probar las consultas
Como quieres probar las consultas, decides que lo suyo es meter una capa de abstracción entre el controlador y EF. Y te creas una interfaz que tiene un nombre parecido a _IBeersService_ o _IBeersRepository_ depende de cual sea el blog al que te haya mandado Google:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">interface</span>&nbsp;<span style="color: #b8d7a3">IBeersService</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt;&gt; GetAll();<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt; GetById(<span style="color: #569cd6">int</span> id);<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span> Add(<span style="color: #4ec9b0">Beer</span> beer);<br />}</pre>

Haces tu implementación (que usa BeersDbContext) y en el controlador le inyectas tu servicio. ¡Genial! Ya está todo listo para que pruebes las consultas… Pues no. Deja de engañarte. Veamos como quedaría el método _Get/id_ del controlador:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">HttpGet</span>(<span style="color: #d69d85">"{id}"</span>)]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IActionResult</span>&gt; Get(<span style="color: #569cd6">int</span> id)<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> beer <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> _svc<span style="color: #b4b4b4">.</span>GetById(id);<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> beer <span style="color: #b4b4b4">!=</span>&nbsp;<span style="color: #569cd6">null</span>&nbsp;<span style="color: #b4b4b4">?</span> (<span style="color: #b8d7a3">IActionResult</span>)Ok(beer) <span style="color: #b4b4b4">:</span> (<span style="color: #b8d7a3">IActionResult</span>)NotFound();<br />}</pre>

Estamos usando el servicio. Fantástico, ahora ya podemos crear un test unitario, para este método del controlador:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">Fact</span>]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span> Get_Beer_By_Id_Should_Return_The_Beer()<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> svc <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Mock</span>&lt;<span style="color: #b8d7a3">IBeersService</span>&gt;();<br />&nbsp;&nbsp;&nbsp; svc<span style="color: #b4b4b4">.</span>Setup(s <span style="color: #b4b4b4">=&gt;</span> s<span style="color: #b4b4b4">.</span>GetById(<span style="color: #b5cea8">1</span>))<span style="color: #b4b4b4">.</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Returns(<span style="color: #569cd6">async</span> () <span style="color: #b4b4b4">=&gt;</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Beer</span>() { Id <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #b5cea8">1</span>, Name <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #d69d85">"mocked beer"</span> });<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> ctlr <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">BeersController</span>(svc<span style="color: #b4b4b4">.</span>Object);<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> result <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> ctlr<span style="color: #b4b4b4">.</span>Get(<span style="color: #b5cea8">1</span>) <span style="color: #569cd6">as</span>&nbsp;<span style="color: #4ec9b0">OkObjectResult</span>;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> beer <span style="color: #b4b4b4">=</span> result<span style="color: #b4b4b4">.</span>Value <span style="color: #569cd6">as</span>&nbsp;<span style="color: #4ec9b0">Beer</span>;<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Assert</span><span style="color: #b4b4b4">.</span>Equal(<span style="color: #b5cea8">1</span>, beer<span style="color: #b4b4b4">?.</span>Id);<br />}</pre>

¿Ves cual es el problema? ¡**Este test NO comprueba absolutamente nada!** Lo único que estás comprobando es que efectivamente el controlador llama al método _GetById_ del servicio. Pero, dado que el servicio es un _mock_ del servicio, no estás probando realmente ninguna consulta. Oh, puedes complicar el _mock_ todo lo que quieras (hay gente que se hace bonitas implementaciones a base de List<T>) pero… estarás probando tu Mock.

**¿Estoy diciendo con eso que siempre debes usar EF desde el controlador?** No. Digo que en este caso tan CRUD, donde **no hay lógica o es solo de validaciones**, lo que debes separar son las validaciones, no EF. En este caso, usar EF directamente desde el controlador es lo más sencillo posible. Y por supuesto, no me he olvidado, quieres probar las consultas: **hazlo a través de tests de integración**. Pero que esas pruebas _usen EF_, no ningun invento que te hayas hecho tu para “mockear EF”.

**<u>2. Proyectos con (relativamente) poca lógica y/o proyectos pequeños</u>**

Antes que nada, **ojo en decidir un arquitectura en base al “tamaño” del proyecto**. El mundo está lleno de “ConsoleApplication1.exe” que han terminado en producción… y de proyectos que eran sencillos (nah… un par de semanas) y que varios meses y múltiples cárnicas, allá siguen.

Usar EF en los controladores directamente, puede funcionar para casos muy CRUD, donde no hay muchas consultas y esas son muy simples. Si en un controlador empezamos a usar LINQ a lo loco, la cosa seguramente no terminará muy bien. En este momento **te puede ir bien encapsular** y sacar EF fuera del controlador. Aunque solo sea por la claridad del código. Por supuesto esto puede ser parte de un _refactoring_ en un proyecto donde se usaba EF en los controladores.

**2.1 Tu enemigo, el repositorio**

Cuando eso ocurre, mucha gente termina a un viejo enemigo muy jodido: **el repositorio genérico**. El repositorio genérico es como _Annatar_: luce muy bien y promete regalos en forma de reutilización de código. Pero creéme… no tardarás en verle su verdadera cara.

Hay varios tipos de repositorios genéricos (muta como los virus) pero en general todos siguen ese patrón:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">interface</span>&nbsp;<span style="color: #b8d7a3">IRepository</span>&lt;<span style="color: #b8d7a3">T</span>, <span style="color: #b8d7a3">K</span>&gt; <span style="color: #569cd6">where</span>&nbsp;<span style="color: #b8d7a3">T</span> : <span style="color: #569cd6">class</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #b8d7a3">T</span>&gt; GetAll();<br />&nbsp;&nbsp;&nbsp; <span style="color: #b8d7a3">T</span> GetById(<span style="color: #b8d7a3">K</span> id);<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">bool</span> Update();<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">void</span> Delete(<span style="color: #b8d7a3">T</span> entity);<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">void</span> Add(<span style="color: #b8d7a3">T</span> entity);<br />}</pre>

Hay gente que incluso, en un alarde de generelización, hasta hacen una implementación genérica del repositorio, algún engendro parecido a eso:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">Repository</span>&lt;<span style="color: #b8d7a3">T</span>, <span style="color: #b8d7a3">K</span>&gt; : <span style="color: #b8d7a3">IRepository</span>&lt;<span style="color: #b8d7a3">T</span>, <span style="color: #b8d7a3">K</span>&gt;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">where</span>&nbsp;<span style="color: #b8d7a3">T</span> : <span style="color: #569cd6">class</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">DbSet</span>&lt;<span style="color: #b8d7a3">T</span>&gt; _set;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">DbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> Repository(<span style="color: #4ec9b0">DbContext</span> context)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _set <span style="color: #b4b4b4">=</span> context<span style="color: #b4b4b4">.</span>Set&lt;<span style="color: #b8d7a3">T</span>&gt;();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _db <span style="color: #b4b4b4">=</span> context;<br /> <br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">bool</span> Add(<span style="color: #b8d7a3">T</span> entity)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _set<span style="color: #b4b4b4">.</span>Add(entity);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>SaveChanges() <span style="color: #b4b4b4">&gt;</span>&nbsp;<span style="color: #b5cea8"></span>;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">bool</span> Delete(<span style="color: #b8d7a3">T</span> entity)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _set<span style="color: #b4b4b4">.</span>Remove(entity);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>SaveChanges() <span style="color: #b4b4b4">&gt;</span>&nbsp;<span style="color: #b5cea8"></span>;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #b8d7a3">T</span>&gt; GetAll()<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _set<span style="color: #b4b4b4">.</span>ToList();<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #b8d7a3">T</span> GetById(<span style="color: #b8d7a3">K</span> id)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _set<span style="color: #b4b4b4">.</span>Find(id);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">bool</span> Update(<span style="color: #b8d7a3">T</span> entity)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> entry <span style="color: #b4b4b4">=</span> _db<span style="color: #b4b4b4">.</span>Entry(entity);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _set<span style="color: #b4b4b4">.</span>Attach(entity);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; entry<span style="color: #b4b4b4">.</span>State <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #b8d7a3">EntityState</span><span style="color: #b4b4b4">.</span>Modified;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> rows <span style="color: #b4b4b4">=</span> _db<span style="color: #b4b4b4">.</span>SaveChanges();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> rows <span style="color: #b4b4b4">&gt;</span>&nbsp;<span style="color: #b5cea8"></span>;<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>

Así luego crear _repositorios_ es tan sencillo como:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">BeersRepository</span> : <span style="color: #4ec9b0">Repository</span>&lt;<span style="color: #4ec9b0">Beer</span>, <span style="color: #569cd6">int</span>&gt;<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> BeersRepository(<span style="color: #4ec9b0">BeersDbContext</span> ctx) : <span style="color: #569cd6">base</span>(ctx)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>

¿Qué puede salir mal con aliados como estos? Pues… todo. Para empezar, a pesar de su nombre, eso no es un repositorio: es un DAO, pues lo único que hace es consultar y modificar elementos de la base de datos. Luego… ¿qué abstracción aporta eso? Pero bueno, aceptamos pulpo, lo usamos y luego nos encontramos el gran problema: Tienes un método en un controlador para agregar una cerveza de una cervecería nueva. Así que usas _BreweryRepository_ para agregar la cervecería y luego _BeerRepository_ para agregar la cerveza. Y entonces te das cuenta que debes usar una transacción porque el método _Add_ de la clase _Repository_ llama al _SaveChanges_. Debes abrir una transacción **para algo que no sería necesario: usando el contexto podrías agregar el objeto Beer a un DbSet, el objeto Brewery a otro DbSet** y llamar a SaveChanges una sola vez. EF ya se encarga de que los cambios se realicen todos o ninguno. También te das cuenta de que añadir 3 cervezas implica llamar a _SaveChanges_ 3 veces. Además hay otros problemas: ¿qué pasa si una entidad no puede borrarse? ¿O el borrado es lógico? Liskov todavía se está riendo…

Lo chungo de eso es que te sueles dar cuenta demasiado tarde. Por supuesto podrías quitar el _SaveChanges_ automático que se hace en cada método y añadir un método _Save()_ en el repositorio (dejando así, más claro si cabe, que nunca ha sido un repositorio de verdad). Esto te soluciona el tema de añadir 3 cervezas, pero en el caso de añadir una cerveza y una brewery… ¿Qué método _Save_ llamas? Al del _BeerRepository_ o al del _BreweryRepository_? Y da gracias que, al menos, nuestro repositorio acepta el DbContext como parámetro, que si lo crease él, entonces sí que date por jodido. Y como digo, te das cuenta demasiado tarde, cuando ya tienes mucho código que depende de ese comportamiento: Annatar se ha quitado la careta y te ha arrastrado a Dol Guldur.

**2.2 DAOs o Servicios**

Olvidémonos del _repositorio_ y por supuesto del _repositorio genérico_ y centrémonos un poco. La idea que hemos comentado antes en 1.1 de usar un servicio no es mala del todo. En el punto 1.1 lo erróneo era el motivo que exibíamos para su creación, no el propio _servicio_ en sí. Por supuesto si el código tiene que ser como el _IBeersService_ que hemos presentado antes, poco nos aporta, pero su existencia **nos permite tener un punto donde colocar esa lógica de negocio y/o consultas que se repiten varias veces.**

Tenemos un servicio _IBeersService_ que contiene todos los métodos necesarios para actualizar, borrar, crear y consultar cervezas, junto con la lógica de negocio para asegurar que todo funciona. Vale, el ejemplo de las cervezas se nos queda un poco corto aquí, pero imagina que en lugar de cervezas tratamos con el apasionante mundo de los pedidos (de cervezas, por supuesto). En este caso crear un pedido, supone crear el pedido junto con determinadas líneas de pedido. Las clases _Order_ y _OrderLine_ son como sigue:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">Order</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">int</span> Id { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">string</span> Name { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">decimal</span> Total { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #b8d7a3">ICollection</span>&lt;<span style="color: #4ec9b0">OrderLine</span>&gt; Items { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />}</pre>

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">OrderLine</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">int</span> Id { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">int</span> OrderId { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Order</span> Order { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">string</span> Name { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">int</span> BeerId { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Beer</span> Beer { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">int</span> Qty { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">decimal</span> LinePrice { <span style="color: #569cd6">get</span>; <span style="color: #569cd6">set</span>; }<br />}</pre>

Y ahora, veamos como puede ser el método _AddOrderLine_ del _IOrderService_:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">void</span> AddOrderLine(<span style="color: #4ec9b0">Order</span> order, <span style="color: #4ec9b0">OrderLine</span> line)<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">if</span> (line<span style="color: #b4b4b4">.</span>Order <span style="color: #b4b4b4">!=</span>&nbsp;<span style="color: #569cd6">null</span>)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">throw</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">ArgumentException</span>(<span style="color: #d69d85">"line"</span>);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; line<span style="color: #b4b4b4">.</span>LinePrice <span style="color: #b4b4b4">=</span> line<span style="color: #b4b4b4">.</span>Beer<span style="color: #b4b4b4">.</span>Price <span style="color: #b4b4b4">*</span> line<span style="color: #b4b4b4">.</span>Qty;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">if</span> (line<span style="color: #b4b4b4">.</span>Qty <span style="color: #b4b4b4">&gt;</span> NeededItemsForDiscount)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; line<span style="color: #b4b4b4">.</span>LinePrice <span style="color: #b4b4b4">=</span> line<span style="color: #b4b4b4">.</span>LinePrice <span style="color: #b4b4b4">*</span> DiscountPercent;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; order<span style="color: #b4b4b4">.</span>Items<span style="color: #b4b4b4">.</span>Add(line);<br />}</pre>

Este método ya gestiona una determinada lógica (en este caso descuentos si el número de elementos comprados sobrepasa una cierta cantidad). Este método además es totalmente testeable, ya que no depende de la base de datos para nada (trabaja solo con las entidades). Así, mientras agreguemos líneas a los pedidos usando el método _AddOrderLine_ todo funcionará correctamente.

Es cierto que en términos de DDD tenemos lo que se llama un [modelo anémico][7]__, pero si somos conscientes de ello y nos está bien pues adelante.

A nivel de dependencias, por supuesto que el _IOrdersService_ depende del contexto de EF y en algunos métodos hará uso de él. P. ej. en un supuesto método que nos devuelva todos los pedidos que incluyan una determinada cerveza (en este caso __db_) es el _BeersDbContext_:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #4ec9b0">Order</span>&gt;&gt; GetOrdersByBeer(<span style="color: #569cd6">int</span> beerId)<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> data <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> _db<span style="color: #b4b4b4">.</span>OrderLines<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Where(ol <span style="color: #b4b4b4">=&gt;</span> ol<span style="color: #b4b4b4">.</span>BeerId <span style="color: #b4b4b4">==</span> beerId)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Select(ol <span style="color: #b4b4b4">=&gt;</span> ol<span style="color: #b4b4b4">.</span>Order)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Distinct()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>ToListAsync();<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> data;<br />}</pre>

A nivel de pruebas unitarias, lo mismo que antes: **para probar este método (consulta) lo mejor es un test de integración**, con EF “de verdad” atacando una BBDD “de verdad”. Es una consulta… poca lógica hay en las consultas.

**2.3 Query objects y comandos**

Vamos ahora a dar un pequeño paso más, conceptualmente simple, pero a la práctica muy potente. Vamos a **separar nuestro servicio en un conjunto de objetos.**

De esos objetos unos se encargarán de las consultas y los otros de las modificaciones. A los primeros los llamaremos _query objects (QO)_ y a los segundos _comandos_.

**2.3.1 Query Objects**

A la práctica usar QO significa que en lugar de tener el método _GetOrdersByBeer_ tienes un objeto que se encarga de esa consulta:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">OrdersByBeerQuery</span> : <span style="color: #b8d7a3">IOrdersByBeerQuery</span><br />{<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> OrdersByBeerQuery(<span style="color: #4ec9b0">BeersDbContext</span> db) <span style="color: #b4b4b4">=&gt;</span> _db <span style="color: #b4b4b4">=</span> db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #4ec9b0">Order</span>&gt;&gt; GetOrdersByBeer(<span style="color: #569cd6">int</span> beerId)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> data <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> _db<span style="color: #b4b4b4">.</span>OrderLines<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Where(ol <span style="color: #b4b4b4">=&gt;</span> ol<span style="color: #b4b4b4">.</span>BeerId <span style="color: #b4b4b4">==</span> beerId)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Select(ol <span style="color: #b4b4b4">=&gt;</span> ol<span style="color: #b4b4b4">.</span>Order)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>Distinct()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #b4b4b4">.</span>ToListAsync();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> data;<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>

(La interfaz no sería estrictamente necesaria).

Luego puedo tener un controlador como el siguiente:

<pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">Route</span>(<span style="color: #d69d85">"api/[controller]"</span>)]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">BeersController</span> : <span style="color: #4ec9b0">Controller</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> BeersController(<span style="color: #4ec9b0">BeersDbContext</span> db) <span style="color: #b4b4b4">=&gt;</span>&nbsp; _db <span style="color: #b4b4b4">=</span> db;<br />&nbsp;&nbsp;&nbsp; [<span style="color: #4ec9b0">HttpGet</span>(<span style="color: #d69d85">"{beerid}/orders"</span>)]<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IActionResult</span>&gt; GetOrders(<span style="color: #569cd6">int</span> beerid)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> query <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">OrdersByBeerQuery</span>(_db);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> data <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> query<span style="color: #b4b4b4">.</span>GetOrdersByBeer(beerid);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> Ok(data);<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>

Todo muy bonito y encapsulado. Excepto por el hecho de que debemos inyectar el contexto al controlador para pasarlo a cada _query object_ que se crea. Si no quieres hacer esto puedes inyectar en el controlador el _query object_ y inyectar el contexto en cada _query object_. El único problema es que si un controlador hace 5 consultas a lo mejor requiere 5 query objects y eso son 5 parámetros en el constructor. Hay dos alternativas para eso:

  1. Agrupas todos los query objects en uno (un _OrdersQueries_ que tenga varios métodos, uno por consulta). En este caso, en el controlador inyectas el _OrderQueries_ y en el _OrderQueries_ inyectas el contexto. 
      * Creas una factoría que te devuelva los _query objects._ Inyectas la factoría en el controlador y el contexto en la factoría. La factoría usa este contexto para crear los _query objects_.</ol> 
    Y sobre testing… pues lo de siempre: son consultas. No hay lógica. Tests de integración.

    **2.3.1.1 ¿Qué devolver? Entidades? DTOs?**

    Esa pregunta es otro clásico. ¿Qué debemos devolver desde los _query objects_ al controlador? Una respuesta rápida es las entidades de base de datos&nbsp; (como en nuestro ejemplo) pero debemos tener presente que podemos tener consultas que requieran _solo parte de los datos_ y en este caso igual deberíamos proyectar solo los datos necesarios. P. ej. podemos tener una consulta BeersForHomePage que devuelva solo nombre e Id, otra consulta BeersForCheckinPage que devuelva más detalles y otra consulta BeersForShopping que devuelva solo Id, Name y Price**.**

    Si devolvemos siempre un _IEnumerable<Beer>_ para todas esas consultas significa que de la BBDD nos hemos traído todas las columnas de la entidad. Si nos queremos traer de la BBDD solo las columnas requeridas entonces podemos usar un DTO:
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">OrdersForListPageQuery</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> OrdersForListPageQuery(<span style="color: #4ec9b0">BeersDbContext</span> db) <span style="color: #b4b4b4">=&gt;</span> _db <span style="color: #b4b4b4">=</span> db;<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #b8d7a3">IEnumerable</span>&lt;<span style="color: #4ec9b0">OrderForListDto</span>&gt; GetAll()<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>Orders<span style="color: #b4b4b4">.</span>Select(o <span style="color: #b4b4b4">=&gt;</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">OrderForListDto</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Name <span style="color: #b4b4b4">=</span> o<span style="color: #b4b4b4">.</span>Name,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Id <span style="color: #b4b4b4">=</span> o<span style="color: #b4b4b4">.</span>Id,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Price <span style="color: #b4b4b4">=</span> o<span style="color: #b4b4b4">.</span>Total<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; })<span style="color: #b4b4b4">.</span>ToList();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    El hándicap es que puedes terminar con muchos DTOs si tienes muchas consultas que solo muestran un subconjunto de los campos de la entidad.

    Otra opción que evita crear esos DTOs es usar _dynamic_ como tipo de retorno de tus query objects. Así, el método _GetAll_ puede devolver _IEnumerable<dynamic>_ y en el Select proyectas sobre un objeto anónimo. El problema es que luego el compilador no puede ayudarte más.

    Es una decisión de esas en la que es fácil tener la sensación de que hagas lo que hagas deberías haberlo hecho distinto 😛

    **2.3.2 Comandos**

    Los comandos incluyen todo lo que no son QOs. Es decir cualquier modificación del estado del sistema. La idea a priori es sencilla:
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommand</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> AddBeerCommand(<span style="color: #4ec9b0">BeersDbContext</span> ctx) <span style="color: #b4b4b4">=&gt;</span> _db <span style="color: #b4b4b4">=</span> ctx;<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Task</span> Execute(<span style="color: #4ec9b0">Beer</span> beer)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _db<span style="color: #b4b4b4">.</span>Beers<span style="color: #b4b4b4">.</span>Add(beer);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>SaveChangesAsync();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    El problema principal es que estamos mezclando dos cosas en esta clase: los _datos del comando_ y _la ejecución del comando_. En este caso los _datos del comando_ son la cerveza que añadimos (modelada con el parámetro a _Execute_) y la ejecución del método es el método _Execute_. Es mucho mejor separarlo y clarificar los conceptos: por un lado el comando y por otro el manejador del comando. El primero es un mero contenedor de datos. El segundo ejecuta la lógica para unos determinado comando.

    Por lo tanto, el código nos quedaría como:
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommand</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Beer</span> BeerToAdd { <span style="color: #569cd6">get</span>; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> AddBeerCommand(<span style="color: #4ec9b0">Beer</span> beerToAdd)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BeerToAdd <span style="color: #b4b4b4">=</span> beerToAdd;<br />&nbsp;&nbsp;&nbsp; }<br />}<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommandHandler</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> AddBeerCommandHandler(<span style="color: #4ec9b0">BeersDbContext</span> ctx) <span style="color: #b4b4b4">=&gt;</span> _db <span style="color: #b4b4b4">=</span> ctx;<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span> Execute(<span style="color: #4ec9b0">AddBeerCommand</span> cmd)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _db<span style="color: #b4b4b4">.</span>Beers<span style="color: #b4b4b4">.</span>Add(cmd<span style="color: #b4b4b4">.</span>BeerToAdd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await </span>_db.SaveChangesAsync();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    Ahora nos falta por ver como sería la acción del controlador:
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">Route</span>(<span style="color: #d69d85">"api/[controller]"</span>)]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">BeersController</span> : <span style="color: #4ec9b0">Controller</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> BeersController(<span style="color: #4ec9b0">BeersDbContext</span> db) <span style="color: #b4b4b4">=&gt;</span>&nbsp; _db <span style="color: #b4b4b4">=</span> db;<br /> <br />&nbsp;&nbsp;&nbsp; [<span style="color: #4ec9b0">HttpPost</span>]<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IActionResult</span>&gt; AddBeer([<span style="color: #4ec9b0">FromBody</span>] <span style="color: #4ec9b0">Beer</span> beer)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> cmd <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommand</span>(beer);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> handler <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommandHandler</span>(_db);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await</span> handler<span style="color: #b4b4b4">.</span>Execute(cmd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> NoContent();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    Observa que **desde el manejador del comando se llama a SaveChanges**. Eso puede parecer que nos acarrea el mismo problema que antes con el repositorio (si quiero agregar tres cervezas o una cerveza y una cerveceria debo usar una transacción) pero la diferencia es que los **comandos no pretenden ser operaciones unitarias**. Es decir, puedes tener un comando que sea “Agregar N cervezas” y otro comando que sea “Agregar cerveza y cervecería”. Así lo lógico es que el guardado final lo realice el manejador del comando cuando haya ejecutado todas las tareas. En general (aunque, por supuesto todo es debatible) suele ser buena idea que una acción del controlador de tu API, termine invocando un solo comando que envuelve toda la lógica de negocio asociada.

    Ahora el tema aquí es que tenemos un acoplamiento entre el controlador y el manejador del comando, por lo que el controlador debe conocer qué manejador maneja cada comando. Veamos como solucionar esto. Observa además que en nuestro caso hemos inyectado al controlador el contexto de EF, a pesar de que realmente el controlador no lo usa (pero lo necesita para crear los manejadores de comandos).

    **2.3.2.1 El bus de comandos (command bus)**

    El bus de comandos es una pieza que nos permite desacoplar el controlador de los manejadores de comandos. Básicamente es una infrastructura que permite algo parecido a publicación/suscripción: los controladores _publican comandos_ y los _manejadores de comando están suscritos a un tipo de comando_. Cuando se publica un comando, el bus automáticamente ejecuta el manejador correspondiente. Suena complicado, pero es una infrastructura muy sencilla (aunque como todo, se puede complicar). Hay implementaciones ya hechas como [MediatR][8].

    Si usamos un bus de comandos entonces el código en el controlador nos queda más o menos así:
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">Route</span>(<span style="color: #d69d85">"api/[controller]"</span>)]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">BeersController</span> : <span style="color: #4ec9b0">Controller</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #b8d7a3">ICommandBus</span> _bus;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> BeersController(<span style="color: #b8d7a3">ICommandBus</span> bus)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _bus <span style="color: #b4b4b4">=</span> bus;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; [<span style="color: #4ec9b0">HttpPost</span>]<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #b8d7a3">IActionResult</span>&gt; AddBeer([<span style="color: #4ec9b0">FromBody</span>] <span style="color: #4ec9b0">Beer</span> beer)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> cmd <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommand</span>(beer);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await</span> _bus<span style="color: #b4b4b4">.</span>Publish(cmd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await</span> _db<span style="color: #b4b4b4">.</span>SaveChangesAsync();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> NoContent();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    La clave aquí es entender que le pasemos el comando que le pasemos, el bus invocará a su correspondiente manejador. El controlador no debe saber que manejador maneja cada comando.

    A nivel de inyección de dependencias, a los manejadores de comandos les inyectamos el contexto, ya que lo necesitan, y al controlador ya no necesitamos inyectarle el contexto de EF: solo el bus de comandos. Eso deja claras las dependencias: el controlador depende del bus de comandos y son los manejadores de comandos quienes dependen del contexto de EF (pues hacen cosas con la BBDD).

    A nivel de unit testing, probar los manejadores de comando es relativamente sencillo **excepto por un problema: dependen del contexto**. Observa el método “_Execute_” del _AddBeerCommandHandler_ anterior. Este método hace su lógica de negocio y termina llamando a SaveChangesAsync. Esta llamada me impide testear fácilmente el método porque requiere EF. El problema ahora es que en el método _Execute_ del manejador de comandos tenemos mezclado el acceso a datos mediante el contexto de EF y la lógica de negocio asociada (que es lo que queremos probar mediante pruebas unitarias). Debemos desacoplar esas dos partes…

    **2.3.2.2 Tu amigo, el repositorio**

    La última pieza que nos falta para solucionar el puzzle es el repositorio. Pero ahora hablamos de un repositorio de verdad, no un DAO camuflado como el infame repositorio que hemos visto antes. Veamos que nos soluciona. Vamos a modificar el manejador de comandos _AddBeerCommandHandler_ para que antes de añadir la cerveza verifique que no exista (y así tenemos una lógica de negocio muy básica que probar):
    
    <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommandHandler</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #b8d7a3">IBeersRepository</span> _repo;<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> AddBeerCommandHandler(<span style="color: #b8d7a3">IBeersRepository</span> repo)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _repo <span style="color: #b4b4b4">=</span> repo;<br />&nbsp;&nbsp;&nbsp; }<br /> <br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span> Execute(<span style="color: #4ec9b0">AddBeerCommand</span> cmd)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> beer <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span> _repo<span style="color: #b4b4b4">.</span>FindByName(cmd<span style="color: #b4b4b4">.</span>BeerToAdd<span style="color: #b4b4b4">.</span>Name);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">if</span> (beer <span style="color: #b4b4b4">!=</span>&nbsp;<span style="color: #569cd6">null</span>)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">throw</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Exception</span>(<span style="color: #d69d85">$"Beer with name </span>{cmd<span style="color: #b4b4b4">.</span>BeerToAdd<span style="color: #b4b4b4">.</span>Name}<span style="color: #d69d85"> already exists"</span>);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await</span> _repo<span style="color: #b4b4b4">.</span>AddBeer(beer);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">await</span> _repo<span style="color: #b4b4b4">.</span>UnitOfWork<span style="color: #b4b4b4">.</span>SaveChangesAsync();<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
    
    Ojo a los cambios:
    
      * Ya **no inyectamos el contexto** en el manejador de comandos. **En su lugar inyectamos un _IBeersRepository_** 
          * **Usamos métodos del repositorio** (_FindByName, AddBeer_). Es el repositorio quien accede a la BBDD. 
              * Llamamos al método **SaveChangesAsync** de la propiedad UnitOfWork del repositorio.</ul> 
            Para que te hagas una idea, la interfaz _IBeersRepository_ es como sigue:
            
            <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">interface</span>&nbsp;<span style="color: #b8d7a3">IBeersRepository</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span> AddBeer(<span style="color: #4ec9b0">Beer</span> beer);<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt; FindById(<span style="color: #569cd6">int</span> id);<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt; FindByName(<span style="color: #569cd6">string</span> name);<br />&nbsp;&nbsp;&nbsp; <span style="color: #b8d7a3">IUnitOfWork</span> UnitOfWork { <span style="color: #569cd6">get</span>; }<br />}</pre>
            
            El repositorio contiene **todas aquellas consultas que son necesarios para los manejadores de comandos y todas las actualizaciones necesarias**. Por norma general un manejador de comandos debería apañárselas con métodos FindByXX (en nuestro caso FindByName). El IBeersRepository no está pensado para consultas, de ahí que no haya un _GetAll_ p.ej.

            Este repositorio es amigable porque por un lado **la implementación de sus métodos nunca llama a SaveChanges.** Veamos como estaría implementado:
            
            <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">class</span>&nbsp;<span style="color: #4ec9b0">BeersRepository</span> : <span style="color: #b8d7a3">IBeersRepository</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">private</span>&nbsp;<span style="color: #569cd6">readonly</span>&nbsp;<span style="color: #4ec9b0">BeersDbContext</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span> BeersRepository(<span style="color: #4ec9b0">BeersDbContext</span> ctx) <span style="color: #b4b4b4">=&gt;</span> _db <span style="color: #b4b4b4">=</span> ctx;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #b8d7a3">IUnitOfWork</span> UnitOfWork <span style="color: #b4b4b4">=&gt;</span> _db;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Task</span> AddBeer(<span style="color: #4ec9b0">Beer</span> beer)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>AddAsync(beer);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt; FindById(<span style="color: #569cd6">int</span> id)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>Beers<span style="color: #b4b4b4">.</span>SingleAsync(b <span style="color: #b4b4b4">=&gt;</span> b<span style="color: #b4b4b4">.</span>Id <span style="color: #b4b4b4">==</span> id);<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">public</span>&nbsp;<span style="color: #4ec9b0">Task</span>&lt;<span style="color: #4ec9b0">Beer</span>&gt; FindByName(<span style="color: #569cd6">string</span> name)<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">return</span> _db<span style="color: #b4b4b4">.</span>Beers<span style="color: #b4b4b4">.</span>SingleAsync(b <span style="color: #b4b4b4">=&gt;</span> b<span style="color: #b4b4b4">.</span>Name <span style="color: #b4b4b4">==</span> name);<br />&nbsp;&nbsp;&nbsp; }<br />}</pre>
            
            Aquí **no hay llamada alguna SaveChanges**. Observa la propiedad _UnitOfWork_ que devuelve… el contexto. En efecto hemos hecho que el contexto implemente la interfaz IUnitOfWork que **solo define el SaveChanges**:
            
            <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro"><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">interface</span>&nbsp;<span style="color: #b8d7a3">IUnitOfWork</span><br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #4ec9b0">Task</span>&lt;<span style="color: #569cd6">int</span>&gt; SaveChangesAsync(<span style="color: #4ec9b0">CancellationToken</span> cancellationToken <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">default</span>(<span style="color: #4ec9b0">CancellationToken</span>));<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">int</span> SaveChanges();<br />}</pre>
            
            (Observa que hemos definido los métodos con el mismo nombre y firma que los métodos de DbContext. Así solo basta declarar que nuestro contexto implementa IUnitOfWork y no tenemos que añadirle método alguno).

            Ahora a nivel de inyección de dependencias, el contexto solo inyecta en los repositorios (y los repositorios a los manejadores de comandos).

            ¿Qué nos permiten esos cambios? Pues **testear fácilmente nuestro manejador de comandos**. Para ello **tan solo debemos pasarle mocks de los repositorios**, que devuelvan los datos que queremos y podemos probar la lógica de negocio:
            
            <pre style="font-family: consolas; background: #1e1e1e; white-space: nowrap; overflow-x: scroll; color: gainsboro">[<span style="color: #4ec9b0">Fact</span>]<br /><span style="color: #569cd6">public</span>&nbsp;<span style="color: #569cd6">async</span>&nbsp;<span style="color: #4ec9b0">Task</span> Add_Beer_With_Existing_Name_Must_Throw_Exception()<br />{<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">const</span>&nbsp;<span style="color: #569cd6">string</span> name <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #d69d85">"Estrella"</span>;<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> uow <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Mock</span>&lt;<span style="color: #b8d7a3">IUnitOfWork</span>&gt;();<br />&nbsp;&nbsp;&nbsp; uow<span style="color: #b4b4b4">.</span>Setup(m <span style="color: #b4b4b4">=&gt;</span> m<span style="color: #b4b4b4">.</span>SaveChangesAsync(<span style="color: #569cd6">default</span>(<span style="color: #4ec9b0">CancellationToken</span>)))<span style="color: #b4b4b4">.</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Returns(() <span style="color: #b4b4b4">=&gt;</span>&nbsp;<span style="color: #4ec9b0">Task</span><span style="color: #b4b4b4">.</span>FromResult(<span style="color: #b5cea8"></span>));<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> repo <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Mock</span>&lt;<span style="color: #b8d7a3">IBeersRepository</span>&gt;();<br />&nbsp;&nbsp;&nbsp; repo<span style="color: #b4b4b4">.</span>Setup(r <span style="color: #b4b4b4">=&gt;</span> r<span style="color: #b4b4b4">.</span>UnitOfWork)<span style="color: #b4b4b4">.</span>Returns(uow<span style="color: #b4b4b4">.</span>Object);<br />&nbsp;&nbsp;&nbsp; repo<span style="color: #b4b4b4">.</span>Setup(r <span style="color: #b4b4b4">=&gt;</span> r<span style="color: #b4b4b4">.</span>FindByName(name))<span style="color: #b4b4b4">.</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Returns(<span style="color: #569cd6">async</span> () <span style="color: #b4b4b4">=&gt;</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Beer</span>()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Id <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #b5cea8">1</span>,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Name <span style="color: #b4b4b4">=</span> name,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Price <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #b5cea8">0.5M</span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> handler <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommandHandler</span>(repo<span style="color: #b4b4b4">.</span>Object);<br />&nbsp;&nbsp;&nbsp; <span style="color: #569cd6">var</span> ex <span style="color: #b4b4b4">=</span>&nbsp;<span style="color: #569cd6">await</span>&nbsp;<span style="color: #4ec9b0">Assert</span><span style="color: #b4b4b4">.</span>ThrowsAsync&lt;<span style="color: #4ec9b0">Exception</span>&gt;(() <span style="color: #b4b4b4">=&gt;</span> handler<span style="color: #b4b4b4">.</span>Execute(<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">AddBeerCommand</span>(<span style="color: #569cd6">new</span>&nbsp;<span style="color: #4ec9b0">Beer</span>() { Name <span style="color: #b4b4b4">=</span> name })));<br />}</pre>
            
            Observa la diferencia de este test con el que se presentó al principio: en este test estamos validando la lógica de negocio del manejador de comandos. No queremos probar que el repositorio funciona.

            La clave aquí está en que el repositorio ahora sí que actúa como tal modificando solo datos en memoria. Es el manejador de comandos quien, al final si todo es correcto, llama a SaveChanges de la “[unit of work][9]” (a la práctica, el contexto). Y es normal que lo haga el manejador de comandos porque las operaciones del repositorio son operaciones básicas CRUD, pero el manejador de comandos no representa una operación básica CRUD: representa una operación de negocio. Así que el manejador actúa con sus repositorios (observa que un manejador de comandos podría usar más de un repositorio si fuese necesario) y solo cuando ha efectuado toda la lógica, consolida los datos en base de datos llamando a SaveChanges.

            **Nota:** En nuestro caso hemos hecho que la “unit of work” se obtenga a partir de una propiedad del repositorio, pero se podría inyectar en los manejadores de comando si se quisiera.

            Para probar los repositorios en si mismos… pues con tests de integración

            **<u>3. En el tintero…</u>**

            Se nos han quedado algunas cosas en el tintero. A partir de lo que tenemos ahora podemos empezar a plantearnos si queremos aplicar más técnicas de DDD. Eso pasaría primero por evitar un modelo anémico (p. ej. el método AddOrderLine podría ser un método de la clase Order). En este caso la lógica de negocio que afecta a una entidad se encuentra en la propia entidad, mientras que la lógica de negocio que afecta a distintos tipos de entidades estaría en los manejadores de comandos.

            También podemos empezar a usar agregados. En este caso, los repositorios deberían ser uno por agregado (trabajando siempre con la raíz del agregado). En nuestro caso, no tendríamos nunca un repositorio de _OrderLines_&nbsp; porque _Order_ y _OrderLines_ forman un agregado cuya raíz es _Order_. Así el repositorio trabajaría solo con _Order_ por lo que los manejadores de comando nunca trabajarían con _OrderLines_ y accederían a ellas solo a través de los métodos de la raíz del agregado. Usando agregados podemos querer evitar las propiedades de navegación entre agregados. P. ej. si _Beer_ y _OrderLine_ no son parte del mismo agregado, quizá nos interesa que _no exista la propiedad Beer_ en _OrderLine_ para así, evitar que se pueda actualizar más de un agregado a la vez y garanticemos que un comando actúa solo sobre un agregado.

            Si un manejador de comandos actúa solo sobre un agregado y una acción de la API lanza un solo comando, ¿como podemos modelar acciones que se dan sobre varios agregados? Aquí nos puede venir bien usar eventos de dominio, que permiten encadenar acciones que afectan a varios agregados. Y si hay un estado global, compartido por varias acciones (que afecten a uno o más agregados) entonces lo podemos modelar como una saga.

            Quizá te interese echar un vistazo a [eShop on Containers][10], que contiene muchas de esas técnicas en acción. Es cierto que quizá la aplicación no es lo suficientemente compleja para justificar algunas de las técnicas aplicadas, pero el objetivo es proporcionar implementaciones “de demo” de esas técnicas, para que puedas ver como funcionan y decidir si quieres aplicarlas o no.

            En fin… hay muchas cosas que se nos han quedado en el tintero, pero creo que en el post hemos cubierto varias opciones. Ahora te toca a ti decidir cuales prefieres aplicar en tus proyectos y recuerda: **no hay balas de plata**.

            Saludos!
Si quieres, puedes invitarme a un café xD

eiximenis
ESCRITO POR
eiximenis
Compulsive Developer