M4‐ Extra: Service Pattern - sjperalta/Web-Service-C-sharp GitHub Wiki
El patrón de diseño que estás utilizando es el Patrón de Servicio (Service Pattern). Este patrón se usa para encapsular la lógica de negocios y de acceso a datos en servicios separados que se pueden reutilizar en múltiples lugares de la aplicación. Al implementar este patrón, logras varias ventajas:
-
Separación de responsabilidades: Mantienes la lógica de negocio y acceso a datos separada de la lógica de presentación o de la API, lo cual mejora la organización del código y facilita su mantenimiento.
-
Reusabilidad: Los servicios pueden ser reutilizados por múltiples controladores o por otros servicios, lo cual reduce la duplicación de código.
-
Facilita las pruebas unitarias: Al tener la lógica encapsulada en servicios, es más fácil escribir pruebas unitarias para esos servicios de manera aislada, sin necesidad de preocuparse por la API o la interfaz de usuario.
-
Desacoplamiento: Al desacoplar la lógica de negocio de los controladores, puedes cambiar la implementación de los servicios sin afectar a los controladores, siempre y cuando la interfaz del servicio no cambie.
En resumen, el uso del Patrón de Servicio mejora la arquitectura y la mantenibilidad de tu aplicación, promoviendo una estructura de código más limpia y modular.
Claro, te mostraré un diagrama simplificado que representa la estructura de tu aplicación utilizando el Patrón de Servicio. El diagrama incluirá los componentes principales: el controlador, el servicio y el contexto de datos (DbContext).
+-----------------------------------------+
| TodoItemsController |
|-----------------------------------------|
| - _todoItemService: ITodoItemService |
|-----------------------------------------|
| + GetTodoItems(): Task<ActionResult> |
| + GetTodoItem(id: long): Task<ActionResult> |
| + PutTodoItem(id: long, item: TodoItem): Task<IActionResult> |
| + PostTodoItem(item: TodoItem): Task<ActionResult> |
| + DeleteTodoItem(id: long): Task<IActionResult> |
+-----------------------------------------+
|
|
v
+-----------------------------------------+
| ITodoService |
|-----------------------------------------|
| + GetTodoItemsAsync(): Task<IEnumerable<TodoItem>> |
| + GetTodoItemAsync(id: long): Task<TodoItem> |
| + UpdateTodoItemAsync(id: long, item: TodoItem): Task<bool> |
| + CreateTodoItemAsync(item: TodoItem): Task<TodoItem> |
| + DeleteTodoItemAsync(id: long): Task<bool> |
| + TodoItemExists(id: long): bool |
+-----------------------------------------+
|
|
v
+-----------------------------------------+
| TodoService |
|-----------------------------------------|
| - _context: TodoContext |
|-----------------------------------------|
| + GetTodoItemsAsync(): Task<IEnumerable<TodoItem>> |
| + GetTodoItemAsync(id: long): Task<TodoItem> |
| + UpdateTodoItemAsync(id: long, item: TodoItem): Task<bool> |
| + CreateTodoItemAsync(item: TodoItem): Task<TodoItem> |
| + DeleteTodoItemAsync(id: long): Task<bool> |
| + TodoItemExists(id: long): bool |
+-----------------------------------------+
|
|
v
+-----------------------------------------+
| TodoContext |
|-----------------------------------------|
| + TodoItems: DbSet<TodoItem> |
+-----------------------------------------+
-
TodoItemsController: Este es el controlador que maneja las solicitudes HTTP. Contiene métodos para obtener, crear, actualizar y eliminar elementos de la lista de tareas (TodoItems). Utiliza una instancia de
ITodoItemService
para delegar la lógica de negocio y acceso a datos. -
ITodoService: Esta es una interfaz que define los métodos que el servicio debe implementar. Proporciona una abstracción que permite al controlador interactuar con la lógica de negocio sin conocer los detalles de su implementación.
-
TodoService: Esta es la implementación de
ITodoItemService
. Contiene la lógica para manejar las operaciones CRUD y utilizaTodoContext
para acceder a la base de datos. -
TodoContext: Este es el contexto de la base de datos que maneja la conexión a la base de datos y proporciona acceso a las entidades (como
TodoItem
) a través deDbSet<TodoItem>
.
Para crear un servicio a partir de los controladores proporcionados, podemos extraer la lógica de acceso a datos y operaciones CRUD en un servicio separado. Esto hará que el código sea más modular y facilite el mantenimiento y las pruebas unitarias.
Primero, definiremos una interfaz para el servicio. Luego, implementaremos esa interfaz en una clase de servicio. Finalmente, modificaremos el controlador para usar el servicio en lugar del contexto de la base de datos directamente.
Crearemos una interfaz llamada ITodoService
que definirá los métodos necesarios.
using System.Collections.Generic;
using System.Threading.Tasks;
using TodoApiRestfull.Models;
namespace TodoApiRestfull.Services
{
public interface ITodoService
{
Task<IEnumerable<TodoItem>> GetTodoItemsAsync();
Task<TodoItem> GetTodoItemAsync(long id);
Task<bool> UpdateTodoItemAsync(long id, TodoItem todoItem);
Task<TodoItem> CreateTodoItemAsync(TodoItem todoItem);
Task<bool> DeleteTodoItemAsync(long id);
bool TodoItemExists(long id);
}
}
Implementaremos la interfaz en una clase llamada TodoItemService
.
using Microsoft.EntityFrameworkCore;
using TodoApiRestfull.Data;
using TodoApiRestfull.Models;
using TodoApiRestfull.Services.Interfaces;
namespace TodoApiRestfull.Services
{
public class TodoService : ITodoService
{
private readonly TodoContext _context;
public TodoService(TodoContext context)
{
_context = context;
}
public async Task<IEnumerable<TodoItem>> GetTodoItemsAsync()
{
return await _context.TodoItems.ToListAsync();
}
public async Task<TodoItem> GetTodoItemAsync(long id)
{
var result = await _context.TodoItems.FindAsync(id);
if(result == null)
{
throw new Exception("Esto es un error porque no existe el registro en la base de datos");
}
return result;
}
public async Task<bool> UpdateTodoItemAsync(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return false;
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
return true;
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return false;
}
else
{
throw;
}
}
}
public async Task<TodoItem> CreateTodoItemAsync(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return todoItem;
}
public async Task<bool> DeleteTodoItemAsync(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return false;
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return true;
}
public bool TodoItemExists(long id)
{
return _context.TodoItems.Any(e => e.Id == id);
}
public async Task<List<TodoItem>> Search(string name)
{
//retornara uno o mas elementos
//select * from dbo.TodoItems t where t.name like '%name%'
var result = await _context.TodoItems
.Where(item => item.Name.Contains(name))
.ToListAsync();
return result;
}
}
}
Modificaremos el controlador TodoItemsController
para que use el servicio ITodoItemService
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApiRestfull.Data;
using TodoApiRestfull.Models;
using TodoApiRestfull.Services.Interfaces;
namespace TodoApiRestfull.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly ITodoService _todoService;
public TodoItemsController(ITodoService service)
{
_todoService = service;
}
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
var items = await _todoService.GetTodoItemsAsync();
return Ok(items);
}
// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _todoService.GetTodoItemAsync(id);
if (todoItem == null)
{
return NotFound();
}
return Ok(todoItem);
}
[HttpGet("search/{name}")]
public async Task<ActionResult<List<TodoItem>>> Search(string name)
{
var result = await _todoService.Search(name);
if(!result.Any()) {
return NotFound();
}
return Ok(result);
}
// PUT: api/TodoItems/5
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
var result = await _todoService.UpdateTodoItemAsync(id, todoItem);
if (!result)
{
return NotFound();
}
return NoContent();
}
// POST: api/TodoItems
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
var createdItem = await _todoService.CreateTodoItemAsync(todoItem);
return CreatedAtAction("GetTodoItem", new { id = createdItem.Id }, createdItem);
}
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var result = await _todoService.DeleteTodoItemAsync(id);
if (!result)
{
return NotFound();
}
return NoContent();
}
}
}
Finalmente, registraremos el servicio en el contenedor de dependencias en Program.cs
.
services.AddControllers();
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddScoped<ITodoItemService, TodoItemService>();
// Otros servicios...
Con esto, hemos abstraído la lógica del controlador en un servicio, lo que mejora la organización del código y facilita su mantenimiento.