2020/05/05

Este no es un artículo sobre DDD

Hace poco publiqué en twitter dos mensajes sobre la relación entre Domain Driven Design y la fase de análisis o diseño que siempre nos recalcaron en la universidad:

DDD es la parte de la programación orientada a objetos en la que más énfasis nos hicieron durante la Universidad, pero que olvidamos por andar pensando en entregar rápido. Ahora toca estudiarla otra vez, para arreglar el despelote que también nos advirtieron que íbamos a armar.
En esa época tenía un nombre menos fancy: le llamaban análisis. Con variantes como “divide y vencerás”, “primero resuelve el problema y luego escribe el código”, “identifica el contexto”, etc.
 
Yo no soy experto en DDD -lo escuché por primera vez en septiembre de 2019-, pero después de estudiarlo un poco vi que muchos de los conceptos ya los conocía. Empezaron a encajar en vacíos que tenía en mi código y caí en cuenta que había cometido muchos errores en la fase de diseño. El principal de ellos: no dedicar suficiente tiempo a identificar todos contextos del problema, es decir, no dividir para vencer... o dividir mal. Esa fase duró casi un mes; no quiero imaginar con qué hubiera terminado de haberla ignorado totalmente.

Quiero hablar de contexto y no entrar en tecnicismos de Bounded Context, Aggregates y el resto del lenguaje que nos propone la metodología. Contexto en el sentido de que una entidad puede ser distinta dependiendo del requerimiento específico en el que estamos trabajando.

¿Qué me alertó? primero, crear una clase que representa la misma entidad pero con menos atributos -hasta allí todo bien- y ponerle un nombre distinto -¡OUCH!-. Este ejemplo puede parecerte familiar:
Supongamos un perfil de usuario con muchos datos de contacto. Es probable que la base de datos tenga una tabla profile con columnas userId, name, lastName, email, address, phoneNumber y bio. Seguramente existe la clase Profile que tiene todos los atributos/propiedades:

public class Profile
{
    public Guid UserId { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Address { get; set; }
    public string PhoneNumber { get; set; }
    public string Bio { get; set; }
}
 

Sabemos de programación orientada a objetos, así que tenemos una clase User muy parecida a esta:

public class User
{
    public Guid Id { get; }
    public Profile Profile { get; }

    private IList<Task> _tasks;
    public IList<Task> Tasks => _tasks.AsReadOnly();

    public User(Guid id, Profile profile)
    {
        Id = id;
        Profile = profile;
        _tasks = new List<Task>();
    }

    public User(Guid id, Profile profile, IEnumerable<Task> tasks)
        : this(id, profile)
    {
        _tasks.AddRange(tasks);
    }

    public void AddTask(Task t)
    { 
        _tasks.Add(t);
    }
}
Pero conforme la aplicación crezca irán apareciendo páginas en donde debemos mostrar información básica del usuario, por ejemplo, el nombre y el correo electrónico. Como sabemos que cargar todo el perfil sería un desperdicio de recursos, podríamos darle vía libre a nuestros némesis: SimpleProfile, ShortProfile, ProfileDetails, DetailedProfile, ExtendedProfile, ... Como sea que se llamen. Y Profile es sólo una de las quince entidades que tenemos.

Piensen por un instante en qué sucede con la clase User, que tiene una referencia a Profile. Sin darnos cuenta nos olvidamos de ella y destruimos la orientación a objetos de nuestro software. ¿O cuál de todas las versiones de Profile usamos allí?. A estas alturas ya tenemos servicios que retornan trozos de información que de alguna forma se enlazan con la interfaz gráfica. La lógica de negocios quedó en esos servicios y no en las entidades, como nos enseñaron. Los métodos entre capas ya no reciben objetos sino parámetros independientes según las distintas funcionalidades. ¿Les parece familiar?.

Qué tal esta invocación:

_ = await _store.CreateProfile(
    id: Guid.NewGuid(),
    name: profile.Name,
    lastname: profile.Lastname,
    email: profile.Email,
    address: profile.Address,
    phoneNumber: profile.PhoneNumber,
    bio: profile.Bio
);
 
Cuando pudo ser:

_ = await _store.SaveProfile(profile);
// o dependiendo del diseño:
_ = await _store.SaveProfile(user.Profile);
 
O qué tal cargar las tareas directamente y mostrarlas en la interfaz:

var tasks = await.GetTasksForUser(id);
var model = tasks.Select(t => MapTaskToViewModel(t));
 
Cuando nos insistieron un montón en respetar las jerarquías:

var user = await GetUser(id);
var model = user.Tasks.Select(t => TaskViewModel.FromTask(t));
 
Allí es cuando debemos parar, tomar lápiz y papel y volver a diseñar. Esta vez recordando que hay distintos contextos, que se vale tener varias clases Profile y que algunas tendrán lógica de negocios y otras serán de sólo lectura. Que es válido retornar o recibir como parámetros instancias de Profile o de otras clases en distintas capas de nuestro software, siempre que estemos en el mismo contexto.

Si la aplicación es pequeña, vale el esfuerzo hacer el refactoring. Si no, es mejor esperar e ir reparando conforme las nuevas funcionalidades lo requieran. Créanme que en muy poco tiempo habrán cubierto todo el código sin tomar riesgos innecesarios.

Seguro hay casos sencillos en donde sirve una entidad como SimpleProfile. Se me ocurre el de mostrar el nombre de usuario y la foto de perfil en el menú. Pero hay que saberlo manejar y ser conscientes de que es una libertad puntual que nos estamos dando como desarrolladores; una excepción y no la regla.

DDD me gusta mucho y creo que todos debemos estudiar -al menos- sus fundamentos, pero debo decir que no es una novedad. Allí no hay nada distinto a lo que aprendí entre 2004 y 2009 en la Universidad. Mis profesores de algoritmos siempre enseñaron lo que debían enseñar: conceptos que nunca cambian. Me acordé mucho de ellos mientras escribía este post.

2020/05/01

Mis indispensables de C#. Parte 3: Configuraciones

Esta es una serie de 3 artículos cortos sobre características de C# que uso en todas mis aplicaciones.

Configurar el serializador de JSON


Cuando quiero usar la misma configuración en toda la aplicación, creo mi propio serializador. Este esquema me permitió migrar fácilmente desde Newtonsoft.Json a System.Text.Json. Reconocimiento especial al método ToHttpJsonContent()... parece que .NET 5 va a incluir extensiones al HttpClient que ayudan a eso.

using System.Net.Http;
using System.Text;
using System.Text.Json;

namespace MyApplication.Helpers
{
    public static class JsonGenerator
    {
        public static readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };

        public static readonly JsonSerializerOptions deserializerOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        };

        public static string Serialize(object obj)
        {
            return JsonSerializer.Serialize(obj, serializerOptions);
        }

        public static StringContent ToHttpJsonContent(object obj)
        {
            return new StringContent(Serialize(obj), Encoding.UTF8, "application/json");
        }

        public static T Deserialize<T>(string content)
        {
            return string.IsNullOrWhiteSpace(content) ?
                default(T) :
                JsonSerializer.Deserialize<T>(content, deserializerOptions);
        }
    }
}

Configurar el cliente HTTP


Usando inyección de dependencias, es posible configurar el IHttpClientFactory con algunos parámetros fijos.

services.AddHttpClient("myClient", client =>
{
    client.BaseAddress = new Uri(Configuration["ConfigGroup:BaseUrl"]);
    client.DefaultRequestHeaders.Add("fn-key-header", Configuration["ConfigGroup:Key"]);
});

services.AddScoped<IMyService, MyService>();

Así se reduce mucho el código del servicio.

public class MyService : IMyService
{
    private readonly IHttpClientFactory _clientFactory;

    public MyService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<MyDTO> GetDTO(Guid id)
    {
        var client = _clientFactory.CreateClient("myClient");
        var response = await client.GetAsync($"/api/functionName/{id}");

        if (response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync();
            var element = JsonGenerator.Deserialize<MyDTO>(body);
            return element;
        }

        return null;
    }
}

2020/04/25

Mis indispensables de C#. Parte 2: Extensiones

Esta es una serie de 3 artículos cortos sobre características de C# que uso en todas mis aplicaciones.

Extensiones… Muchas extensiones


Me gusta usar extension methods para validaciones. Aquí hay algunas básicas.

public static class ValueExtensions
{
    public static string ThrowIfEmpty(this string value)
    {
        return string.IsNullOrWhiteSpace(value) ?
            throw new ArgumentNullException() :
            value.Trim();
    }

    public static string TrimOrThrow(this string value)
    {
        return value?.Trim() ??
            throw new ArgumentNullException();
    }

    public static Guid ThrowIfEmpty(this Guid value)
    {
        return value == Guid.Empty ?
            throw new ArgumentNullException() :
            value;
    }

    public static DateTime ThrowIfInPast(this DateTime value)
    {
        return value <= DateTime.Now ?
            throw new ArgumentOutOfRangeException() :
            value;
    }

    public static DateTime NowIfDefault(this DateTime value)
    {
        return value == default(DateTime) ?
            DateTime.Now :
            value;
    }
}

Es muy sencillo validar parámetros usando estas extensiones.

public async Task<TransferObject> DoSomething(
    string userId,
    Guid roleId,
    string name,
    DateTime meetingDate,
    DateTime date)
{
    userId.ThrowIfEmpty();
    roleId.ThrowIfEmpty();
    name = name.TrimOrThrow();
    meetingDate.ThrowIfInPast();
    date = date.NowIfDefault();

    return await _store.SaveSomething(userId, roleId, name, meetingDate, date);
}

También las uso para interactuar con la base de datos, si por algún motivo no estoy usando un ORM.

public static class SqlExtensions
{
    public static string GetStringOrNull(this SqlDataReader reader, int index)
    {
        return reader.IsDBNull(index) ?
            null :
            reader.GetString(index);
    }

    public static DateTime GetDateTimeOrNow(this SqlDataReader reader, int index)
    {
        return reader.IsDBNull(index) ?
            DateTime.Now :
            reader.GetDateTime(index);
    }

    public static DateTime? GetDateTimeOrNull(this SqlDataReader reader, int index)
    {
        return reader.IsDBNull(index) ?
            null :
            reader.GetDateTime(index);
    }

    public static Guid GetGuidOrEmpty(this SqlDataReader reader, int index)
    {
        return reader.IsDBNull(index) ?
            Guid.Empty :
            reader.GetGuid(index);
    }

    public static Guid? GetGuidOrNull(this SqlDataReader reader, int index)
    {
        return reader.IsDBNull(index) ?
            null :
            reader.GetGuid(index);
    }

    public static void SetSqlDBNullParameters(this SqlCommand command)
    {
        foreach (IDataParameter param in command.Parameters)
        {
            if (param.Value == null)
            {
                param.Value = DBNull.Value;
            }
        }
    }
}

Así es muy fácil manejar las columnas opcionales de un insert.

string itemKey = "blog.lvbernal.com";
string itemValue = null;

string nonQuery = @"
    INSERT INTO [some].[table] ([key], [value])
    VALUES (@key, @value)";

using SqlConnection conn = new SqlConnection(_connectionString);
conn.Open();

using SqlCommand cmd = new SqlCommand(nonQuery, conn);
cmd.Parameters.AddWithValue("@key", itemKey);
cmd.Parameters.AddWithValue("@value", itemValue);  // Esto genera un error

cmd.SetSqlDBNullParameters();  // Aquí reemplazamos los null por DBNull.Value

Y leer datos desde un SqlDataReader.

using SqlDataReader reader = await cmd.ExecuteReaderAsync();

if (reader.HasRows)
{
    while (reader.Read())
    {
        var item = new ItemDTO(
            id: reader.GetGuid(0),
            name: reader.GetStringOrNull(1),
            owner: reader.GetGuidOrNull(2)
        );

        list.Add(item);
    }
}

2020/04/23

Mis indispensables de C#. Parte 1

Esta es una serie de 3 artículos cortos sobre características de C# que uso en todas mis aplicaciones... aunque este en particular empieza con una nueva.

Switch expressions


Las switch expressions son mi característica favorita de C# 8. Ayudan a asignar valores rápidamente y las uso mucho para renderizar la interfaz gráfica.

Supongamos una Partial View que muestra el nombre de una empresa, consultor o institución, y un botón de color distinto para cada caso.

@model MyViewModel

@{
    var (title, color) = Model.RoleType switch
    {
        RoleType.Company => ("Empresa", "info"),
        RoleType.Institution => ("Institución", "primary"),
        RoleType.Mentor => ("Consultor", "warning"),
        _ => ("?", "danger")
    };
}

<h3>
    @Model.Name
</h3>

<button class="btn btn-@color">
    @title
</button>

También funciona con métodos, si no tenemos una partial para cada item.

@model MenuViewModel

@{
    string GetLinkIcon (string linkPage) => linkPage switch
    {
        MenuViewModel.PageIndex => "fe fe-home",
        MenuViewModel.PageTools => "fe fe-box",
        MenuViewModel.PagePrograms => "fe fe-arrow-up-right",
        MenuViewModel.PageConnections => "fe fe-users",
        MenuViewModel.PageRequests => "fe fe-bell",
        _ => ""
    };

    string GetLinkCls (string linkPage) => linkPage == Model.Current ? "active" : "";
}

<ul class="nav flex-column d-none d-md-block">
    @foreach (var p in Model.Pages)
    {
        <li class="nav-item @GetLinkCls(p)">
            <a class="nav-link" asp-action="@p">
                <span class="@GetLinkIcon(p)"></span>
            </a>
        </li>
    }
</ul>

Propiedades de sólo lectura


Nos acostumbramos tanto a las propiedades con get y set públicos, que las escribimos de forma automática y se nos olvida que las clases son el primer filtro para evitar errores en capas inferiores.

Podemos usar un set privado si la propiedad es de sólo lectura.

public string Name { get; private set; }

Pero yo prefiero quitar el set para que se asigne únicamente desde el constructor y forzar la regla de negocio.

public string Name { get; }

public MyClass(string name)
{
    Name = name;
}

2020/04/19

Aprovechando las Partial Views en ASP.NET Core (Parte 1)

Si trabajan con ASP.NET Core, seguramente han separado los componentes de su interfaz gráfica usando Partial Views o Partials. Son una característica básica del framework junto a las Sections y los Layouts.

Cuando se empezaron a popularizar los frameworks de front-end como Angular, Ractive, React o Vue (y recientemente el estándar WebAssembly), las partials quedaron en un segundo plano porque al ser renderizadas desde el servidor, "no son dinámicas". Ese dinamismo lo están agregando con los Razor Components y Blazor, pero esa es otra historia.

Y aquí viene la opinión impopular: muy pocas aplicaciones son tan complejas como para justificar un framework de front-end. A eso agregaría: muy pocas aplicaciones son tan complejas como para justificar una conexión permanente con el servidor, que es como funcionan los Razor Components. Y una más: que un elemento aparezca o desaparezca de una lista, sin recargar la página, no justifica ninguna complejidad accidental.

Con un poco de javascript se pueden aprovechar las partials para construir aplicaciones web suficientemente dinámicas.

Supongamos una aplicación que permite asignar objetivos a una empresa. Cada usuario puede tener múltiples empresas, pero las administra de forma independiente. El controlador responde a la URL:

/{controller=Company}/{companyId:guid}/{action=Index}/{id?}

El método (action) Index se ve más o menos así:

public async Task<IActionResult> Index(Guid companyId)
{
    var objectives = await _objectiveService.GetObjectives(UserId, companyId);
    var model = objectives.Select(o => ObjectiveViewModel.FromObjective(o));
    return View(model);
}

La view usa un @foreach para renderizar la lista desde el servidor y cada vez que agregamos un nuevo objetivo, se recarga la página. El sitio se demora en cargar y no hay ningún spinner. ¡Horror!. ¿Justifica usar un framework de front-end? Yo creo que no. ¿Justifica hacer todo con javascript y web APIs? Tampoco. Sigamos usando las herramientas que tiene el framework.

Lo primero es crear un método que retorna la lista de objetivos ya renderizada:

public async Task<IActionResult> ObjectivesComponent(Guid companyId)
{
    var objectives = await _objectiveService.GetObjectives(UserId, companyId);
    var model = objectives.Select(o => ObjectiveViewModel.FromObjective(o));
    return PartialView("_ObjectivesPartial", model);
}

Podemos simplificar el Index:

public IActionResult Index(Guid companyId)
{
    var model = new IndexViewModel(companyId);
    return View(model);
}

La view tiene un contenedor que muestra el spinner y carga otras dos partials, una con el script y otra con el modal que se despliega para agregar un nuevo objetivo:

@model IndexViewModel

<div id="objectivesHolder">
    <div class="spinner-border text-primary" role="status">
        <span class="sr-only">Cargando...</span>
    </div>
</div>

<button data-toggle="modal" data-target="#addObjectiveModal" class="btn btn-primary">
    Agregar objetivo
</button>

@section Scripts
{
    @{ await Html.RenderPartialAsync("_IndexScripts", Model.Id); }
}

@section EndOfPage
{
    @{ await Html.RenderPartialAsync("_AddObjectiveModal"); }
}

La partial _IndexScripts tiene un manejador de interfaz muy pequeño:

@model Guid

<script text="text/javascript">
    $(() => {
        // No me gusta inyectar valores del modelo directamente al script,
        // así que los paso como parámetros y desde un solo sitio
        var mrg = new IndexUIMgr("@Html.Raw(Model)");
    });

    class IndexUIMgr {
        constructor(id) {
            this.companyId = id;
            this.configureModal();
            this.loadObjectives();
        }

        configureModal() { }

        async loadObjectives() { }
    }
</script>

El método que carga la partial es loadObjectives:

async loadObjectives() {
    let response = await fetch(`/company/${this.companyId}/objectivesComponent`);

    if(response.ok) {
        let content = await response.text();
        $("#objectivesHolder").html(content);
        // Habilitar otros botones, por ejemplo,
        // los de editar o completar objetivo
    } else {
        $("#objectivesHolder").html("No fue posible cargar el contenido");
    }
}

¡Listo! Ya cargamos la partial de forma dinámica. Ahora sigue el botón de agregar. Yo uso un método genérico para habilitar los formularios:

enableModalForm(modalSelector, submitBtnSelector, formSelector, callback) {
    let modal = $(modalSelector);
    let btn = $(submitBtnSelector);
    let form = $(formSelector);

    btn.on("click", () => {

        // jquery-validation
        if (!form.valid())
        {
            return false;
        }

        form.submit((ev)=>{
            // Evitar múltiples clicks
            btn.prop("disabled", true);

            // Submit sin recargar la página
            $.ajax({
                type: form.attr("method"),
                url: form.attr("action"),
                data: form.serialize(),
                success: (data) => {
                    modal.modal("hide");
                    form.trigger("reset");
                    form.off("submit");
                    callback();
                    btn.prop("disabled", false);
                },
                error: (err) => {
                    form.off("submit");
                    btn.prop("disabled", false);
                }
            });

            ev.preventDefault();
        });
    });
}

El método configureModal llama a enableModalForm:

let context = this;
context.enableModalForm(
    "#addObjectiveModal",
    "#addObjectiveBtn",
    "#addObjectiveForm",
    () => { context.loadObjectives(); });

Y por último, el endpoint para recibir el formulario:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<bool> AddObjective(Guid companyId, AddObjectiveViewModel model)
{
    var created = await _objectiveService.AddObjective(
        userId: UserId,
        companyId: companyId,
        title: model.Title
    );

    return created;
}

Para el botón de editar, basta con llenar el formulario (ej. #editObjectiveForm) antes de mostrar el modal; el resto es igual. Si los formularios son muy grandes, se puede integrar un template engine que ayude a mapear los valores, pero creo que en ese caso es mejor usar una página separada.

Cuando se agrega un nuevo objetivo, el componente se actualiza completamente. La página no se recarga y el scroll no se va hacia arriba. No hay que preocuparse por renderizar un elemento falso mientras se crea "por detrás" el definitivo.

Esta idea no es nueva y hay herramientas muy interesantes como turbolinks que ayudan en el proceso. Pero me parece que en ASP.NET no está suficientemente documentada.

¿Cuándo usar todo un framework? En mi opinión, cuando hay un builder como el de Lucidchart, para herramientas colaborativas como Google Docs, o para aplicaciones en donde hay muchos parámetros e interacciones, como en los buscadores. ¿Qué tanta interacción se puede manejar sin framework? Puedo decir que la suficiente. Todo este sitio está hecho así:


2020/04/14

¿Y qué pasó con CaliSharp?

¡Por supuesto que seguimos! Solo que le perdí el interés a publicar fotos. Creo que el ejercicio consiste en invitar a quienes más podamos a los eventos y compartir con los que lleguen. Crear comunidad.

Llegó un momento, que según mi Instagram fue por allá en septiembre de 2019, en el que ese último paso de publicar fotos y artículos de agradecimiento me empezó a sonar a “mira de lo que te perdiste por no asistir”, o peor, “mira lo que hice este mes”. Así que paré.

Desde noviembre tuvimos charlas como “Desarrollo de plataformas”, “Introducción a radio definida por software”, “Azure Functions: Experiencias de un principiante”, “Súper tendencias de arquitectura y desarrollo de software para 2020”, “¿Debería dar una charla?”, “Blazor: C# en el browser”, “Tecnología para Industria 4.0” y “REST, y por qué lo estás haciendo mal”. Y esperamos seguir organizando muchas más.

Recuerden que pueden inscribirse al meetup CaliSharp para conocer los próximos eventos. Y si les interesa dar una charla, sólo deben llenar este formulario, que tiene un aire vintage pero funciona muy bien.

PD: Vienen artículos técnicos sobre .NET Core.