Rest API Template 만들기 - EP 02 (Vertical Slice, MediatR)

2022. 12. 22. 00:00ASPNET/ASPNET 7

반응형

Vertical Slice Architecture

UI, Application, Domain, DB 등의 각각을 Layer 로 분리 하여 따로 관리 하지 않고

기능별(user story or feature) 로 모아서 응집도를 높이는 Architecture

https://jimmybogard.com/vertical-slice-architecture/

참고 : https://jimmybogard.com/vertical-slice-architecture/

 

일단 Controllers 폴더와  WeatherForecastController.cs 를 삭제하자

WeatherForecast.cs 파일도 삭제하자

 

자 위와 같은 Architecture 를 구현해보자

 

Features 폴더생성

User 폴더 생성

UserController.cs 생성

 

초기에 Vertical Slice 를 구현 하기 전에 UserController.cs 에 모든 코드를 집중해 보자.

UserController.cs

using Microsoft.AspNetCore.Mvc;

namespace ApiServer.Features.User;
[ApiController]
[Route("[controller]")]

public class UserController : ControllerBase
{
    [HttpPost(Name = "Login")]
    public IActionResult Login(string id, string password)
    {
        if (password == "1234")
        {
            return Ok();
        }
        return Unauthorized();
    }
}

실행해 보자

아래와 같이 입력 하면

 

다음과 같이 에러가 발생한다. (401)

password 를 1234 로 바꾸면

특별한 response 없이 HttpsStatus 200 을 return 한다. 

 

코드를 분리하기전에 MediatR 이라는 library 를 설치해보자

(Event Driven 을 구현하기 위한 좋은 library 이다. )

 

Ctrl + ` 또는 View --> Terminal click --> 개발자 명령 프롬프트로 이동

개발자 명령 프롬프트에서 프로젝트 폴더로 이동

**이미 프로젝트 폴더에 있다면 이동하지 않아도 된다. **

(현재 [솔루션명].sln 파일이 있는 폴더라면 [프로젝트명].csproj 파일이 있는 폴더로 이동)

다음 실행

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

 

Program.cs 에 다음 코드 추가

using MediatR;
…
builder.Services.AddMediatR(AssemblyHelper.GetAllAssemblies().ToArray());

 

Helper 폴더 추가 --> AssemblyHelper.cs 추가 -->코드 수정

using System.Reflection;
using System.Runtime.Loader;

namespace ApiServer.Helper;

public class AssemblyHelper
{
    public static List<Assembly> GetAllAssemblies(SearchOption searchOption = SearchOption.TopDirectoryOnly)
    {
        var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        var assemblyFiles = Directory.GetFiles(baseDirectory
        , "*.dll"
        , searchOption);
        var path = Directory.GetFiles(baseDirectory);
        foreach (string assemblyPath in assemblyFiles)
        {
            try
            {
                var assembly = AssemblyLoadContext
                .Default
                .LoadFromAssemblyPath(assemblyPath);
            }
            catch (Exception ex)
            {
                //Debug.WriteLine(ex.ToString());
            }
        }
        return AssemblyLoadContext.Default.Assemblies.ToList();
    }
}

 

일단 이렇게 만든 상태에서 UserController.cs 에 있는 코드들을 분리해보자

 

Features/User/Login.cs

using MediatR;
using System.Runtime.CompilerServices;

namespace ApiServer.Features.User;

public class Login
{
    public class Command : IRequest<Response>
    {
        public string Id { get; set; } = string.Empty;
        public string Pasword { get; set; } = string.Empty;
    }
    public class Response
    {
        public bool Result { get; set; }
        public Error? Error { get; set; }
        public string? AccessToken { get; set; }
    }

    public class Error
    {
        public string Code { get; set; } = string.Empty;
        public string Message { get; set; } = string.Empty;
    }

    public class CommandHandler : IRequestHandler<Command, Response>
    {
        public Task<Response> Handle(Command request, CancellationToken cancellationToken)
        {
            var response = new Response { Result = false };
            string? accessToken = null;
            
            if (request.Pasword == "1234")
            {
                accessToken = Guid.NewGuid().ToString("N");
                response.Result = true;
            }
            else
            {
                response.Error = new Error()
                {
                    Code = "02",
                    Message = $"Login Failed, check your password"
                };
            }
            response.AccessToken = accessToken;
            return Task.FromResult(response);
        }
    }


}

 

이제 UserController 을 수정해 보자

Features/User/UserController.cs

using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ApiServer.Features.User;
[ApiController]
[Route("[controller]")]

public class UserController : ControllerBase
{
    private readonly IMediator _mediator;

    public UserController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [AllowAnonymous]
    [ProducesResponseType(typeof(Login.Response), StatusCodes.Status200OK)]
    [HttpPost("login")]
    public async Task<IActionResult> Login(Login.Command request)
    {
        if (!ModelState.IsValid)
            return BadRequest();

        var response = await _mediator.Send(request);

        return response.Result ? Ok(response) : Unauthorized(); //status code 로 response처리
        //return Ok(response); // 내부의 result 와 error 를 통해 response 처리
    }
}

 

관련영상

https://youtu.be/YaZckvUOWi0

 

 

반응형