En mi trabajo surgió la necesidad de migrar una aplicación relativamente grande que estaba construida con ASP.NET MVC 5 + DevExtreme. Yo también había participado en el desarrollo de dicha aplicación y la verdad es que conforme fue creciendo me fui dando cuenta de que tal ves el camino que habíamos elegido no era el correcto, MVC nos estaba generando controladores gigantescos incluso cuando toda la lógica de negocio estaba en la base de datos, ya que aunque usábamos repositorios para el acceso a datos, todo el sistema estaba basado en procedimientos almacenados y los repositorios solo se encargaban de llamarlos.
Entonces, al presentarse la necesidad de crear una nueva versión del mismo sistema vi que Blazor Server era una buena opción, ya que echaba de menos la característica que ofrecía WebForms de poder ordenar las vistas en carpetas y Blazor Server nos permitía hacerlo, ademas otra necesidad principal que teníamos es que el sistema nuevo debía ser responsivo y se optó por usar MudBlazor.
Cómo nació la idea de la arquitectura
Necesitaba una arquitectura que cumpliera con tres requisitos muy concretos:
- Que fuera ordenada
- Que fuera fácil de mantener
- Que no fuera difícil de implementar
Así que me dedique un tiempo a investigar distintas arquitecturas de software como DDD o Core-Driven pero no me terminaban de convencer. Pero si me dieron una idea de como conseguir lo que necesitaba.
La Arquitectura Multicapa Simplificada
Supongamos que la aplicación que vamos a migrar se llama Rocket, la arquitectura se basa en cuatro capas que estarían en proyectos separados:
Rocket.Core → Modelos, DTOs, Requests/Responses y clases compartidas
Rocket.Data → Repositorios y acceso a base de datos
Rocket.Services → Lógica de negocio y orquestación
Rocket.Web → Capa de presentación (Blazor Server)
La idea es simple, cada capa tiene una responsabilidad clara y la única capa que puede relacionarse con las demás(que de hecho debe estar relacionada) es Core.

Cómo se comunican las capas
La relación es directa y limpia:
UI (Rocket.Web)
↓
Servicios (Rocket.Services)
↓
Repositorios (Rocket.Data)
↓
Base de datos
-----------------------------
Rocket.Core → Consumido por todas las capas
Rocket.Core
Esta capa esta compuesta de al menos los siguientes namespaces
Rocket.Core
- Rocket.Core.Dtos → Clases para transferir datos
- Rocket.Core.IO → Datos de entrada y salida de los servicios
Una característica que me pareció necesaria al momento de diseñar esta arquitectura es que estuviera desacoplada de la capa de presentación, ya que anteriormente tuve la experiencia de que se había desarrollado un sistema con WebForms y después se nos solicito crear WebAPIs que expusieran algunas funcionalidades del sistema.
Por esta razón agregue un Result Wrapper que me ayude separar la capa de servicios de la capa de presentacion y en caso de ser necesario exponer funcionalidades con WebAPIs solo se consumen los servicios y ¡listo!.
Result
--------------
Success:bool → Indica si la operación fue exitosa
Message:string → Mensaje que se mostrará al usuario
Data:T (Opcional) → Objeto con la respuesta que devuelve el servicio
El código de la clase Result es el siguiente:
namespace Rocket.Core.IO;
public class Result
{
protected Result() { }
public bool Success { get; protected set; }
public string Message { get; protected set; }
public static Result Ok(string message = "") =>
new() { Success = true, Message = message };
public static Result Fail(string message) =>
new() { Success = false, Message = message };
}
public class Result<T>() : Result where T : BaseResponse?
{
public T? Data { get; set; }
public static Result<T> Ok(T data, string message = "")
=> new Result<T> { Success = true, Message = message, Data = data };
public new static Result<T> Fail(string message)
=> new Result<T> { Success = false, Message = message, Data = default };
}Rocket.Data
Esta capa es la encargada del acceso a datos, aquí se encuentran los repositorios que usarán los servicios junto con el ORM que se deseé utilizar, en mi caso actualmente uso Dapper pero podría ser cualquier otro, y esta capa se compone de al menos el siguiente namespace:
Rocket.Data
- Rocket.Data.Repositories → Aquí estarían todos los repositorios que requiera nuestra aplicación
Rocket.Services
Para mi esta es la capa más importante ya que aquí tendríamos las reglas de negocio, validaciones, cálculos, etc.
Esta capa estaría compuesto por al menos los siguientes namespaces:
Rocket.Services → Aquí estarían los servicios de nuestra aplicación, por ejemplo: ClientesService, PagosService, etc.
La firma típica de los métodos de cada servicio serian algo parecido a lo siguiente:
public Result<TResponse> Metodo(TRequest request);
// Ejemplo
public Result<LoginResponse> Login(LoginRequest request);De esta forma, en caso de ser necesario agregar un parámetro nuevo a la función simplemente se define una propiedad en la clase Request y se haría lo mismo para agregar un campo más en la respuesta pero en la clase Response correspondiente.
Un punto importante
Aunque ésta arquitectura permite usar interfaces para los servicios y repositorios actualmente solo agregué clases concretas ya que donde trabajo no usamos pruebas unitarias y me topé con un poco de resistencia a incluirlas.
Rocket.Web
Esta es la capa de presentación, en mi caso es una aplicación Blazor Server pero bien podría ser una aplicación MAUI o un WebAPI pero lo importante es que este proyecto no debe contener lógica de negocio, solo se encarga de la interacción del usuario con nuestra aplicación.
Conclusión
Esta arquitectura multicapa simplificada nació de una necesidad de mantener orden en un sistema relativamente grande sin caer en complejidad innecesaria. Actualmente es la forma en que estructuró las aplicaciones .NET porque ofrece un equilibrio entre claridad, simplicidad y capacidad de crecimiento.




