# Obsesión Primitiva: Simplificando la Complejidad

La Obsesión Primitiva es un término que denota la excesiva dependencia de tipos de datos primitivos para representar conceptos complejos del dominio en el desarrollo de software. Este artículo abordará las implicaciones de esta práctica y cómo superarla para lograr una arquitectura de software más robusta y mantenible.

La Obsesión Primitiva se refiere al uso generalizado de tipos de datos primitivos (como int, string, bool) en sistemas de software para representar conceptos de dominio complejos. Aunque inicialmente parece conveniente, este enfoque conduce a problemas como falta de expresividad, aumento de la complejidad y dificultades en el mantenimiento.

# **Cuándo y por qué superar la Obsesión Primitiva**

* **Cuándo:** Es crucial superarla cuando la representación de conceptos complejos del dominio se vuelve confusa y el código es difícil de mantener.
    
* **Por qué:** Superar esta práctica mejora la expresividad del código, facilita la validación y reduce la duplicación, resultando en un software más robusto y fácil de mantener.
    

# **Técnicas para abordar la Obsesión Primitiva**

Antes de profundizar en la implementación específica, me gustaría establecer una base sólida mediante la introducción de dos clases útiles: `Error` y `Result`. Estas clases nos permitirán manejar de manera eficiente y coherente los resultados y errores a lo largo de nuestra aplicación, asegurando que el código sea robusto y fácil de mantener.

## **Clase Error**

```csharp
public record Error(string Code, string Name)
{
    public static Error None = new(string.Empty, string.Empty);

    public static Error NullValue = new("Error.NullValue", "Null value was provided");
}
```

Se utiliza para encapsular información relacionada con errores que pueden ocurrir durante la ejecución de nuestro programa. Esta clase es un registro, lo que significa que es inmutable y su estado no cambia una vez que se ha creado una instancia.

* `Code`: Un identificador único para el tipo de error.
    
* `Message`: Un mensaje descriptivo asociado con el error.
    

La clase también incluye errores predefinidos como `None` (que representa la ausencia de un error) y `NullValue` (que se utiliza cuando se proporciona un valor nulo donde no se espera).

## **Clase Result**

Es fundamental para el manejo de operaciones que pueden tener éxito o fallar. Esta clase encapsula el resultado de una operación, junto con cualquier error que pueda haber ocurrido.

```csharp
public class Result
{
    protected internal Result(bool isSuccess, Error error)
    {
        if (isSuccess && error != Error.None)
        {
            throw new InvalidOperationException();
        }

        if (!isSuccess && error == Error.None)
        {
            throw new InvalidOperationException();
        }

        IsSuccess = isSuccess;
        Error = error;
    }

    public bool IsSuccess { get; }

    public bool IsFailure => !IsSuccess;

    public Error Error { get; }

    public static Result Success() => new(true, Error.None);

    public static Result Failure(Error error) => new(false, error);

    public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None);

    public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error);

    public static Result<TValue> Create<TValue>(TValue? value) =>
        value is not null ? Success(value) : Failure<TValue>(Error.NullValue);
}

public class Result<TValue> : Result
{
    private readonly TValue? _value;

    protected internal Result(TValue? value, bool isSuccess, Error error)
        : base(isSuccess, error)
    {
        _value = value;
    }

    [NotNull]
    public TValue Value => IsSuccess
        ? _value!
        : throw new InvalidOperationException("The value of a failure result can not be accessed.");

    public static implicit operator Result<TValue>(TValue? value) => Create(value);
}
```

**Constructor Protegido Interno:**

El constructor de la clase `Result` es protegido e interno, lo que significa que sólo puede ser llamado desde dentro de la clase o desde clases derivadas. Acepta dos parámetros: `isSuccess` (un booleano que indica si la operación fue exitosa) y `error` (un objeto `Error` que contiene información sobre un error si ocurrió).

**Validaciones en el Constructor:**

Dentro del constructor, hay validaciones para asegurarse de que el estado del objeto sea coherente: si `isSuccess` es `true`, entonces `error` debe ser `Error.None`, y si `isSuccess` es `false`, entonces `error` no debe ser `Error.None`. Si estas condiciones no se cumplen, se lanza una excepción `InvalidOperationException`.

**Propiedades:**

* `IsSuccess`: Un valor booleano que indica si la operación fue exitosa.
    
* `IsFailure`: Un valor booleano derivado que indica si la operación falló.
    
* `Error`: Un objeto de tipo `Error` que contiene detalles del error si la operación falló.
    

**Métodos Estáticos de Fábrica:**

* `Success()`: Crea un resultado exitoso sin valor.
    
* `Failure(error)`: Crea un resultado fallido con un error específico.
    
* `Success<TValue>(value)`: Crea un resultado exitoso con un valor de tipo `TValue`.
    
* `Failure<TValue>(error)`: Crea un resultado fallido de tipo `TValue`.
    
* `Create<TValue>(value)`: Crea un resultado basado en si `value` es nulo o no.
    

**Clase Genérica** `Result<TValue>` :

* Esta clase derivada permite manejar resultados que incluyen un valor de tipo `TValue`.
    
* Tiene un campo privado `_value` para almacenar el valor.
    
* El constructor de esta clase derivada también verifica la consistencia del resultado.
    
* La propiedad `Value` devuelve el valor si `IsSuccess` es `true`; de lo contrario, lanza una excepción, asegurando que los valores de los resultados fallidos no se puedan acceder.
    

**Conversión Implícita:**

Hay un operador de conversión implícita que permite convertir un valor de tipo `TValue` en un objeto `Result<TValue>`. Esto simplifica la creación de resultados exitosos.

Esta clase `Result` ofrece métodos estáticos para crear instancias que representen éxitos (`Success`) o fracasos (`Failure`). También se proporciona una sobrecarga genérica de `Result` para operaciones que devuelven un valor.

El uso de estas clases en conjunto proporciona un marco claro y consistente para manejar los resultados y errores en una aplicación, lo que facilita la escritura de código más limpio y mantenible.

# Implementación

Crea clases o estructuras personalizadas para encapsular datos primitivos y comportamientos relacionados, mejorando la expresividad y conteniendo la lógica de validación.

Ahora si, veamos como podemos implementar las diferentes técnicas.

## **Técnica #1: Con clase abstracta**

**Clase Abstracta ValueObject:**

`ValueObject` es una clase abstracta, lo que significa que no puede ser instanciada por sí misma. En su lugar, está diseñada para ser heredada por otras clases que representan objetos de valor.

```csharp
public abstract class ValueObject : IEquatable<ValueObject>
{
    public abstract IEnumerable<object> GetAtomicValues();

    public bool Equals(ValueObject? other)
    {
        return other is not null && ValuesAreEqual(other);
    }

    public override bool Equals(object? obj)
    {
        return obj is ValueObject other && ValuesAreEqual(other);
    }

    public override int GetHashCode()
    {
        return GetAtomicValues().Aggregate(default(int), HashCode.Combine);
    }

    private bool ValuesAreEqual(ValueObject other)
    {
        return GetAtomicValues().SequenceEqual(other.GetAtomicValues());
    }
}
```

**Interfaz** `IEquatable<ValueObject>` **:**

La clase implementa la interfaz `IEquatable<ValueObject>`, lo que significa que proporciona una implementación personalizada para determinar si dos objetos de valor son iguales.

**Método Abstracto** `GetAtomicValues` :

* Este método abstracto debe ser implementado por las clases derivadas para devolver los valores individuales (atómicos) que componen el objeto de valor.
    
* Estos valores son los que se utilizan para determinar la igualdad entre dos instancias.
    

**Método** `Equals` **(Sobrecarga de** `IEquatable<ValueObject>`**):**

* Este método determina la igualdad con otro `ValueObject`.
    
* Utiliza `ValuesAreEqual` para comparar los valores atómicos de ambos objetos.
    
* Devuelve `false` si el otro objeto es nulo.
    

**Método** `Equals` **(Sobrecarga de** `Object`**):**

* Esta sobrecarga asegura que la igualdad se maneje correctamente cuando el objeto se trata como un `Object`.
    
* Realiza una comprobación de tipo antes de llamar a `ValuesAreEqual`.
    

**Método** `GetHashCode` :

* Sobrescribe el método `GetHashCode` de `Object`.
    
* Proporciona un código hash que se basa en los valores atómicos del objeto, utilizando `Aggregate` y `HashCode.Combine`.
    
* Este código hash es consistente con la definición de igualdad de la clase.
    

**Método Privado** `ValuesAreEqual` :

* Compara los valores atómicos de `this` y otro `ValueObject` para determinar si son iguales.
    
* Utiliza `SequenceEqual` para comparar las secuencias de valores atómicos.
    

En resumen, `ValueObject` es una clase base diseñada para proporcionar una implementación consistente y robusta de igualdad basada en valores atómicos para objetos de valor. Esta clase es esencial para garantizar que la igualdad de objetos en tu dominio se base en sus valores y no en sus referencias de memoria, lo cual es un aspecto clave en el diseño orientado al dominio y en la creación de modelos de dominio ricos y expresivos.

**Clase EmailAddress:**

Esta es una implementación de ejemplo para entender como podemos utilizar nuestra clase abstracta de `ValueObject`.

```csharp
public sealed class EmailAddress : ValueObject
{
    public const int MaxLength = 100; // Longitud máxima típica para un email

    private EmailAddress(string value)
    {
        Value = value;
    }

    public string Value { get; }

    public static Result<EmailAddress> Create(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
        {
            return Result.Failure<EmailAddress>(new Error(
                "EmailAddress.Empty",
                "Email address is empty."));
        }

        if (email.Length > MaxLength)
        {
            return Result.Failure<EmailAddress>(new Error(
                "EmailAddress.TooLong",
                "Email address is too long."));
        }

        if (!IsValidEmail(email))
        {
            return Result.Failure<EmailAddress>(new Error(
                "EmailAddress.Invalid",
                "Email address is not valid."));
        }

        return new EmailAddress(email);
    }

    public override IEnumerable<object> GetAtomicValues()
    {
        yield return Value;
    }

    private static bool IsValidEmail(string email)
    {
        // Patrón de regex para validar el correo electrónico
        var emailRegex = @"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
        var regex = new Regex(emailRegex, RegexOptions.IgnoreCase);
        
        return regex.IsMatch(email);
    }
}
```

**Herencia de** `ValueObject` :

Al heredar de `ValueObject`, `EmailAddress` obtiene una implementación de igualdad basada en el valor, esencial para objetos de valor como una dirección de correo electrónico.

**Propiedad** `MaxLength` :

Define una constante `MaxLength` para establecer la longitud máxima permitida para una dirección de correo electrónico, lo que ayuda a mantener la validez y la integridad de los datos.

**Constructor Privado:**

El constructor es privado y solo se llama dentro de la clase, lo que garantiza que las instancias de `EmailAddress` solo puedan ser creadas a través del método `Create`, asegurando que todas las direcciones de correo electrónico pasen por las validaciones necesarias.

**Propiedad** `Value` :

Almacena el valor de la dirección de correo electrónico, manteniendo la inmutabilidad del objeto después de su creación.

**Método Estático** `Create` :

* Proporciona un punto de creación controlado para instancias de `EmailAddress`.
    
* Realiza varias validaciones: verifica que el correo electrónico no esté vacío, que no exceda la longitud máxima y que sea válido según una expresión regular.
    
* Devuelve un objeto `Result<EmailAddress>`, permitiendo un manejo elegante de errores o un éxito en la creación del objeto.
    

**Sobrescritura de** `GetAtomicValues` :

Implementa el método abstracto heredado de `ValueObject`, devolviendo los componentes que definen la igualdad del objeto, en este caso, el valor de la dirección de correo electrónico.

**Método Privado Estático** `IsValidEmail` :

* Utiliza una expresión regular para validar el formato de la dirección de correo electrónico.
    
* Proporciona una verificación de formato adicional y esencial para garantizar la validez del correo electrónico.
    

En resumen, `EmailAddress` encapsula de manera efectiva las características y validaciones necesarias para manejar direcciones de correo electrónico en una aplicación. Al integrarse con `ValueObject` y `Result`, esta clase no solo garantiza la validez y la integridad de las direcciones de correo electrónico sino que también facilita la gestión de errores y el mantenimiento de la inmutabilidad de los objetos.

### Ejemplo de uso de la ténica #1

```csharp
public class User
{
    // Constructor(es)

    // Otras propiedades

    public Email Email { get; set; }

    // Métodos
}
```

```csharp
var user = new User();
var email = EmailAddress.Create("khanakat@email.com");

if (email.IsFailure)
{
    Console.WriteLine(email.Error);
    return;
}

user.Email = email.Value;
Console.WriteLine(user.Email.Value);
```

## **Técnica #2: Con clase record**

Utiliza Value Objects para representar conceptos del dominio definidos por sus atributos en lugar de su identidad. Estos son inmutables y su igualdad se basa en el valor de sus propiedades.

Por ejemplo podemos utilizar una clase record la cual ya contiene la implementación de `IEquatable` por ende ya no es necesario implementar una clase abstracta.  
De una manera muy sencilla podría ser de la siguiente manera:

```csharp
public sealed record FirstName(string Value);
```

Pero si quisieramos que nuestro `ValueObject` sea mas robusto podriamos hacerlo de la siguiente manera:

```csharp
public sealed record FirstName
{
    public const int MaxLength = 50;

    private FirstName(string value)
    {
        Value = value;
    }

    public string Value { get; }

    public static Result<FirstName> Create(string firstName)
    {
        if (string.IsNullOrWhiteSpace(firstName))
        {
            return Result.Failure<FirstName>(new Error(
                "FirstName.Empty",
                "First name is empty."));
        }

        if (firstName.Length > MaxLength)
        {
            return Result.Failure<FirstName>(new Error(
                "FirstName.TooLong",
                "First name is too long."));
        }

        return new FirstName(firstName);
    }
}
```

**Uso de** `record` :

Al ser un `record`, `FirstName` es inmutable por diseño. Los registros en C# son tipos de referencia que proporcionan valor semántico de igualdad y son ideales para modelar objetos inmutables.

**Constante** `MaxLength` :

La constante `MaxLength` define la longitud máxima permitida para un nombre, lo que ayuda a mantener la validez y consistencia de los datos.

**Constructor:**

El constructor es privado y establece el valor del nombre. Se espera que las instancias sean creadas mediante el método estático `Create`.

**Propiedad** `Value` :

Almacena el valor del nombre de pila, garantizando que una vez que se crea una instancia de `FirstName`, su valor no puede ser modificado.

**Método Estático** `Create` :

* Este método es el punto de creación recomendado para instancias de `FirstName`.
    
* Realiza validaciones para asegurarse de que el nombre de pila proporcionado no esté vacío y no exceda la longitud máxima establecida.
    
* En caso de error (nombre vacío o demasiado largo), devuelve un objeto `Result<FirstName>` indicando el fallo, junto con un mensaje de error apropiado.
    
* Si las validaciones son exitosas, devuelve una nueva instancia de `FirstName`.
    

En resumen el diseño de la clase `FirstName` garantiza que todos los nombres de pila manejados por tu sistema cumplan con las reglas definidas, evitando estados inválidos y facilitando el manejo de errores. Al utilizar un enfoque basado en registros y métodos estáticos para la creación y validación, esta clase promueve un uso seguro y coherente de los nombres de pila en toda tu aplicación.

### Ejemplo de uso de la ténica #2

```csharp
public class User
{
    // Constructor(es)

    // Otras propiedades

    public FirstName FirstName { get; set; }

    // Métodos
}
```

```csharp
var user = new User();
var firstName = FirstName.Create("Fernando");

if (firstName.IsFailure)
{
    Console.WriteLine(firstName.Error);
    return;
}

user.FirstName = firstName.Value;
Console.WriteLine(user.FirstName.Value);
```

# **Conclusión**

**Conclusión: La Obsesión Primitiva y el Arte de la Abstracción Efectiva**

Para concluir, es esencial recordar que superar la Obsesión Primitiva no es solo una cuestión de aplicar técnicas; es un aspecto fundamental en la evolución de nuestras habilidades como desarrolladores. Cada vez que elegimos representar un concepto complejo del dominio de manera más abstracta y significativa, no solo estamos escribiendo código más limpio y mantenible, sino también perfeccionando nuestro arte.

Este artículo es solo un paso en el viaje hacia una mejor comprensión y aplicación de prácticas de desarrollo de software eficaces. Espero que este enfoque en la Obsesión Primitiva te haya inspirado a reflexionar sobre cómo representas los conceptos en tus proyectos y a buscar constantemente formas de mejorar tu código.

Nos vemos en futuros artículos donde continuaremos desglosando los principios del diseño de software limpio y su aplicación práctica en el mundo del desarrollo. ¡Hasta entonces, feliz codificación y recuerda que cada línea de código cuenta!

😸 Happy Coding!
