洋葱架构

发布时间:2023-05-22

一、什么是洋葱架构

洋葱架构(Onion Architecture)是由Jeffrey Palermo提出的一种软件架构模式,用于实现可扩展、可维护和可测的应用程序。 它的基本思想是将应用程序分为多个层,每个层都依赖于下一层,并通过依赖注入实现。 这种层次结构就像一个洋葱,每一层都像是裹在洋葱外层的一层,通过不断向内扩展,就能构建出一个稳定、可扩展的应用程序架构。

二、洋葱架构的结构

洋葱架构可以分为四个主要的层:

1. UI层

UI层是应用程序的用户接口部分,可以是Web应用、桌面应用或移动设备应用。 UI层负责与用户交互,接收用户输入,向外部系统显示信息、状态等。但它并不负责任何业务逻辑,它只是将请求转发给下一层。

2. 应用程序层

应用程序层包含应用程序的业务逻辑和应用程序流程。它接收由UI层传递过来的请求,将数据处理后传递到下一层。 应用程序层中的方法需要具备可复用性,它们应该是独立的,这样可以在其它应用程序中重复使用。

3. 领域层

领域层定义了业务逻辑和实体。它是业务处理的核心,包含了业务逻辑的实现代码。 领域层的主要作用是封装所有与业务相关的代码,并对外表现出一套干净、简单的接口。因此它需要与具体的技术实现解耦,以便于将来的维护和替换。

4. 基础设施层

基础设施层提供了应用程序所需的所有支持,例如数据库、文件系统、网络连接和邮件等。 基础设施层负责将领域层和外部系统连接起来,它是业务逻辑和外部系统之间的桥梁,为领域层提供必要的支持。

三、洋葱架构的优点

洋葱架构的优点主要有以下几点:

1. 可扩展性

由于每个层都是独立的,它们之间的耦合很松散,容易扩展。可以在每个层中添加更多的组件或逻辑,而不影响其它层的功能。

2. 可测试性

洋葱架构可以很容易地进行单元测试、集成测试和功能测试,因为每个层都是可测试的。

3. 可维护性

由于业务逻辑封装在领域层中,所以更容易进行维护、修复和升级。

4. 可重用性

由于每个层都是独立的,因此它们可以在多个项目中重复使用。

四、示例代码

1. 用户服务接口

public interface IUserService
{
    bool ValidateUserCredentials(string username, string password);
    User GetUserById(int id);
    IEnumerable<User> GetAllUsers();
    void CreateUser(User user);
}

2. 用户服务实现

public class UserService : IUserService
{
    private readonly IUserRepository userRepository;
    public UserService(IUserRepository userRepository)
    {
        this.userRepository = userRepository;
    }
    public bool ValidateUserCredentials(string username, string password)
    {
        return userRepository
            .GetUserByUsername(username)?
            .Password
            .Equals(password) ?? false;
    }
    public User GetUserById(int id)
    {
        return userRepository.GetUserById(id);
    }
    public IEnumerable<User> GetAllUsers()
    {
        return userRepository.GetAllUsers();
    }
    public void CreateUser(User user)
    {
        userRepository.CreateUser(user);
    }
}

3. 用户仓储接口

public interface IUserRepository
{
    User GetUserById(int id);
    IEnumerable<User> GetAllUsers();
    void CreateUser(User user);
    User GetUserByUsername(string username);
}

4. 用户仓储实现

public class UserRepository : IUserRepository
{
    private readonly ApplicationDbContext context;
    public UserRepository(ApplicationDbContext context)
    {
        this.context = context ?? throw new ArgumentNullException(nameof(context));
    }
    public User GetUserById(int id)
    {
        return context.Users.FirstOrDefault(u => u.Id == id);
    }
    public IEnumerable<User> GetAllUsers()
    {
        return context.Users.ToList();
    }
    public void CreateUser(User user)
    {
        context.Users.Add(user);
        context.SaveChanges();
    }
    public User GetUserByUsername(string username)
    {
        return context.Users.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
    }
}

5. 控制器

[ApiController]
public class UserController : ControllerBase
{
    private readonly IUserService userService;
    public UserController(IUserService userService)
    {
        this.userService = userService;
    }
    [HttpPost]
    [Route("api/users")]
    public IActionResult CreateUser([FromBody] CreateUserRequest request)
    {
        var user = new User { Username = request.Username, Password = request.Password };
        userService.CreateUser(user);
        return Ok();
    }
    [HttpGet]
    [Route("api/users")]
    public IActionResult GetAllUsers()
    {
        var users = userService.GetAllUsers();
        var userDtos = users.Select(u => new UserDto { Id = u.Id, Username = u.Username });
        return Ok(userDtos);
    }
    [HttpGet]
    [Route("api/users/{id}")]
    public IActionResult GetUserById(int id)
    {
        var user = userService.GetUserById(id);
        var userDto = new UserDto { Id = user.Id, Username = user.Username };
        return Ok(userDto);
    }
    [HttpPost]
    [Route("oauth/token")]
    public IActionResult GetToken([FromBody] GetTokenRequest request)
    {
        if (userService.ValidateUserCredentials(request.Username, request.Password))
        {
            return Ok(new { AccessToken = "dummyAccessToken" });
        }
        else
        {
            return Unauthorized();
        }
    }
}