옷차림이 단정하다.
소개하다.
2016년에 제가 속한 회사는 우리의 주요 소프트웨어 세트에 대해 중대한 개작을 진행했습니다.우리는 교육회사이기 때문에 이 소프트웨어는 성적표, 출근, 과정, 평가, 행위, 일정 등 학교 관리자나 교사가 필요로 하는 모든 것을 포함한다.이것은 정말 큰 성공이다.교사와 행정관들은 이 소프트웨어를 좋아해서 앞으로 나아갈 수 있는 큰 걸음으로 여기는 것 같다.우리는 모두 자랑스럽다.그러나 시간이 지날수록 빛은 점점 사라진다.우리가 받기 시작한 흔한 불평은 사용자 인터페이스가 혼란스럽고 결함이 있으며 내비게이션이 어렵고 시스템 운행이 느리다는 것이다.
우리가 조사 속도가 느리다고 시작했을 때, 우리는 각종 API에 대한 데이터 호출이 매우 느리다는 것을 알아차렸다.2018년 말 어느 때api에 대한 데이터 호출을 가속화하는 방법을 연구하기 시작했습니다.그때NetCore2.0 은 이미 출시되었지만 현재 사용하고 있습니다.4.5.3 순이다.따라서 기존 API를 개조하는 대신 완전히 XML로 작성된 새 API 레이어를 0부터 시작하기로 했습니다.순핵심.
알아요...알아요...우리는 0에서부터 이 결정의 옳고 그름을 토론할 수 있다.우리의 모든 시간을 절약하기 위해서, 우리는 계속해서 새 창고의 최종 용도에 대해 토론할 것이다.
사람들이 잘 아는 유류 창고는 바로 여기에 있다.Net 4.5.3은 솔리드 프레임워크와 클래식 저장소/작업셀 모드를 사용합니다.Core에서 솔리드 프레임을 사용하는 경우Net Core 는 쿼리 속도를 향상시키지 못했습니다.그들은 여전히 매우 느리다.보통 250ms 이상의 구역에서 조회를 진행합니다.실체 프레임워크는 이 '새 창고' 코드를 위해 그것을 삭감하지 않을 것이다.해보기로 했어요 Dapper.Dapper 외에도 백엔드 코드와 제공 데이터를 다른 모델로 구축하기로 결정했습니다.CQRS(명령 조회-책임 분리)에 관한 많은 내용을 읽고 인터넷에서 좋은 예를 찾은 후에 우리는 이 모델을 확정했다.This article 특히 유용하다.비록 우리가 그것을 완전히 따르지는 않았지만, 우리는 그 속에서 많은 생각을 훔쳤다.
본고의 나머지 부분은 우리가 현재 어떻게 API 코드를 설정하고 따르는 모델을 소개하는 예이다.
비밀 번호
이 예시의 모든 원본 코드를 https://github.com/MelodicDevelopment/example-dotnet-api-cqrs 에서 얻을 수 있습니다.우리는 다음에 코드의 조직을 다시 토론할 수 있다.나는 곧 예를 하나 들고 싶다.그러나 이것은 우리 팀의 관심과 매우 가깝다.
만약 이 API 프로젝트를 실행한다면, 이것은 내가 이 예를 위해 만든 간단한 API를 기록하는 큰 페이지를 볼 수 있을 것이다.
계약
contracts 프로젝트는 해결 방안의 모든 인터페이스와 DTO를 포함합니다.예제 DTO는 저자 및 서적입니다.CQRS 모드를 따르기로 결정했기 때문에 데이터베이스 쿼리와 명령은 서로 다른 클래스로 분류됩니다.질의는 IQuery 인터페이스를 기반으로 합니다.
dotnetapi-cqrs。계약/데이터/iquiry.대테러 엘리트
using System.Data;
namespace dotnet_api_cqrs.contracts.data
{
public interface IQuery<T>
{
string Sql { get; set; }
T Execute(IDbContext context, IDbTransaction transaction = null);
}
}
명령은 ICommand 인터페이스를 기반으로 합니다.ICommand는 IQuery 인터페이스의 간단한 확장자입니다. 모든 명령이 정수로 되돌아오기 때문에, 일반적인 확장자는 int입니다.dotnetapi-cqrs。계약/데이터/ICommand.대테러 엘리트
namespace dotnet_api_cqrs.contracts.data
{
public interface ICommand : IQuery<int> { }
}
IDbContext는 우리의 데이터베이스 연결 클래스로 우리의 조회와 명령을 실행할 것입니다.데이터 프로젝트에서 실현되기 때문에 우리는 뒤에서 더 많은 것을 토론할 것이다.dotnetapi-cqrs。계약/데이터/상하문.대테러 엘리트
using System;
using System.Collections.Generic;
using System.Data;
namespace dotnet_api_cqrs.contracts.data
{
public interface IDbContext : IDisposable
{
T QueryFirst<T>(string query, object param = null, CommandType commandType = CommandType.Text, IDbTransaction transaction = null);
IEnumerable<T> Query<T>(string query, object param = null, CommandType commandType = CommandType.Text, IDbTransaction transaction = null);
int InsertSingle(string sql, object param = null, CommandType commandType = CommandType.Text, IDbTransaction transaction = null, int? timeout = null);
int Command(string sql, object param = null, CommandType commandType = CommandType.Text, IDbTransaction transaction = null, int? timeout = null);
T Transaction<T>(Func<IDbTransaction, T> query);
void Transaction(Action<IDbTransaction> query);
}
}
일부 인터페이스에facade라는 이름도 포함되어 있다는 것을 알게 될 것이다.각 facade 인터페이스는 Func 또는 Action 의뢰를 반환하는 방법을 정의합니다.우리가 데이터 프로젝트에 대해 이야기할 때, 우리는 이것들을 더 많이 소개할 것이다.데이터:명령 및 질의
데이터 항목에서, 당신은 우리의 데이터 접근층 주위의 대다수 조직을 볼 수 있습니다.명령과 질의에 사용되는 처음 두 폴더는 각 DTO에 사용되는 하위 폴더입니다.DTO 폴더에 질의 클래스가 표시됩니다.각 질의는 해당 기능에 따라 이름이 지정됩니다.예를 들면 다음과 같습니다.
dotnetapi-cqrs。data/Queries/Book/GetAllBooksQuery。대테러 엘리트
using System.Collections.Generic;
using System.Data;
using dotnet_api_cqrs.contracts.data;
using D = dotnet_api_cqrs.contracts.dto;
namespace dotnet_api_cqrs.data.Queries.Book
{
public class GetAllBooksQuery : IQuery<IEnumerable<D.Book>>
{
public string Sql { get; set; }
public GetAllBooksQuery()
{
Sql = @$"
SELECT BookID,
Title,
CopyRightYear,
AuthorID
FROM dbo.Books
ORDER BY Title";
}
public IEnumerable<D.Book> Execute(IDbContext context, IDbTransaction transaction = null)
{
return context.Query<D.Book>(Sql, transaction: transaction);
}
}
}
GetAllBooksQuery 클래스는 IQuery > 인터페이스를 구현합니다.우리가 그것을 실행하려고 할 때, IDbContext를execute 방법에 전달합니다.아주 간단합니다.이제 InsertBookCommand 클래스를 살펴보겠습니다.dotnetapi-cqrs。data/Commands/Book/InsertBookCommand。대테러 엘리트
using System.Data;
using dotnet_api_cqrs.contracts.data;
using D = dotnet_api_cqrs.contracts.dto;
namespace dotnet_api_cqrs.data.Commands.Book
{
public class InsertBookCommand : ICommand
{
private readonly D.Book _book;
public string Sql { get; set; }
public InsertBookCommand(D.Book book)
{
_book = book;
Sql = $@"
INSERT INTO dbo.Books
(Title, CopyRightYear, AuthorID)
VALUES (@Title, @CopyRightYear, AuthorID);";
}
public int Execute(IDbContext context, IDbTransaction transaction = null)
{
var param = new {
Title = _book.Title,
CopyRightYear = _book.CopyRightYear,
AuthorID = _book.AuthorID
};
return context.InsertSingle(Sql, param, transaction: transaction);
}
}
}
이 클래스는 책 파라미터를 수신하고, sql 파라미터를 설정한 다음, IDbContext를 사용하여 그것을 실행합니다.InsertSingle 메서드는 새 책을 실제로 반환하고 새 ID를 반환합니다.데이터:전면
우리가 처음에 이런 모델을 사용하여 개발을 시작했을 때, 우리는 서비스에서 검색과 명령을 직접 사용하기 시작했고, 그것들을 주입으로 설정할 필요가 없었다.우리는 단지 서비스 코드에서 만들었을 뿐이다
new GetAllBooksQuery()
.사실이 증명하듯이 이것은 테스트하기 매우 어렵다.그래서 이 문제를 해결하기 위해 우리는 외벽을 사용하기로 결정했다.Facades는 하나의 DTO나 하나의 논리 DTO와 관련된 조회와 명령을 조합할 뿐입니다.서비스와facades에서 해결하고자 하는 또 다른 도전은 데이터베이스 상하문을 읽거나 쓰는 능력입니다.그래서 우리는 항상facade 방법에서 함수 의뢰를 되돌려 주는 모델을 채택했다.이 프로세스를 단순화하기 위해 하위 클래스에 사용되는 몇 가지 방법을 포함하는 Facade 기본 클래스를 만들었습니다.질의나 명령에서 값을 반환하려면 Func 위임을 사용할 수 있습니다.만약 우리가 조회나 명령의 반환 값을 필요로 하지 않는다면, 우리는 Action 의뢰를 사용할 것입니다.모든 함수는 하나의 IDbContext와 가능한 IDbTransaction을 의뢰합니다.이것은 조회나 명령을 실행하기 위해 다른 상하문에서 전달할 수 있도록 합니다.transaction에 전송하면 같은 업무에서 여러 개의 데이터베이스 작업을 수행할 수 있습니다.DbContext는 트랜잭션의 각 질의와 명령을 실행하도록 설정됩니다.DbContext에서 Transaction 방법을 사용하여 여러 데이터베이스 작업을 수행할 수 있습니다.dotnetapi-cqrs。데이터/모양새.대테러 엘리트
using System;
using System.Data;
using dotnet_api_cqrs.contracts.data;
namespace dotnet_api_cqrs.data
{
public class Facade
{
protected Func<IDbContext, IDbTransaction, T> Prepare<T>(IQuery<T> queryCommand)
{
return (context, transaction) => {
return queryCommand.Execute(context, transaction);
};
}
protected Action<IDbContext, IDbTransaction> Prepare(ICommand command)
{
return (context, transaction) => {
command.Execute(context, transaction);
};
}
}
}
BookFacade는 실천에서의 외관을 보여주는 직관적인 예이다.마찬가지로facades는 어떠한 업무 논리도 포함하지 않습니다.DTO 또는 DTO 논리 그룹과 관련된 쿼리와 명령만 그룹화할 수 있습니다.using System;
using System.Collections.Generic;
using System.Data;
using dotnet_api_cqrs.contracts.data;
using dotnet_api_cqrs.contracts.dto;
using dotnet_api_cqrs.data.Commands.Book;
using dotnet_api_cqrs.data.Queries.Book;
namespace dotnet_api_cqrs.data
{
public class BookFacade : Facade, IBookFacade
{
public Func<IDbContext, IDbTransaction, IEnumerable<Book>> GetBooks()
{
return Prepare(new GetAllBooksQuery());
}
public Func<IDbContext, IDbTransaction, Book> GetBook(int bookID)
{
return Prepare(new GetBookQuery(bookID));
}
public Func<IDbContext, IDbTransaction, IEnumerable<Book>> GetBooksForAuthor(int authorID)
{
return Prepare(new GetBooksForAuthorQuery(authorID));
}
public Func<IDbContext, IDbTransaction, int> InsertBook(Book book)
{
return Prepare<int>(new InsertBookCommand(book));
}
public Action<IDbContext, IDbTransaction> DeleteBook(int bookID)
{
return Prepare(new DeleteBookCommand(bookID));
}
}
}c#
서비스
서비스 항목은 우리가 발생해야 할 모든 업무 논리를 놓는 곳이다.우리가 Dapper와 CQRS를 사용할 때, 우리는 많은 업무 논리가 우리가 데이터베이스에서 우리가 원하는 내용을 정확하게 검색했다는 사실에 의해 처리된다는 것을 발견했다.데이터베이스에 있는 모든 테이블을 나타내는 실체도 없고, 이 실체를 처리할 저장소도 없습니다.우리는 우리가 원하는 열만 있으면, 간단한 테이블 연결을 할 수 있고, 실체 프레임워크와 저장소 모델의 모든 템플릿 파일을 필요로 하지 않고, 우리가 원하는 방식으로 우리의 업무 모델을 만들 수 있다.우리의 실제 코드는 저장 과정을 실행하고 여러 개의 데이터 집합을 되돌려 주는 방법이 있지만, 간단하게 보기 위해서, 나는 여기에 이러한 방법을 포함하지 않았다.따라서 최종적으로 우리의 서비스는 통상적으로 매우 간소화되어 보이지만, 그것들은 확실히 업무 논리가 발생하는 곳이다.이러한 서비스들은 하나의 기본 서비스 유형을 확장하여 여러 데이터베이스 상하문에서 읽기와 쓰기를 할 수 있다.우리는 우리의 외관을 서비스에 주입할 것이다.이 특수한 예에 대해 나는 단지 하나의 상하문만을 사용하여 모든 읽기와 쓰기를 완성할 뿐이다.
dotnetapi-cqrs。서적 서비스.대테러 엘리트
using System.Collections.Generic;
using dotnet_api_cqrs.contracts.data;
using dotnet_api_cqrs.contracts.dto;
using dotnet_api_cqrs.contracts.services;
namespace dotnet_api_cqrs.services
{
public class BookService : Service, IBookService
{
private readonly IBookFacade _bookFacade;
public BookService(IDbContext context, IBookFacade bookFacade) : base(context)
{
_bookFacade = bookFacade;
}
public IEnumerable<Book> GetAllBooks()
{
return _bookFacade.GetBooks()(Context, null);
}
public Book GetBook(int bookID)
{
return _bookFacade.GetBook(bookID)(Context, null);
}
public IEnumerable<Book> GetBooksForAuthor(int authorID)
{
return _bookFacade.GetBooksForAuthor(authorID)(Context, null);
}
public Book InsertBook(Book book)
{
var newBookID = _bookFacade.InsertBook(book)(Context, null);
return _bookFacade.GetBook(newBookID)(Context, null);
}
public void DeleteBook(int bookID)
{
_bookFacade.DeleteBook(bookID)(Context, null);
}
public Book ReplaceBook(Book oldBook, Book newBook)
{
return Context.Transaction<Book>(_transaction => {
_bookFacade.DeleteBook(oldBook.BookID)(Context, _transaction);
var newBookID = _bookFacade.InsertBook(newBook)(Context, _transaction);
return _bookFacade.GetBook(newBookID)(Context, _transaction);
});
}
}
}
주사하다
나는 너무 깊이 걷지 않을 것이다.데이터와 서비스 항목은 주입된 외관과 서비스를 설정하는 데 사용되는 정적 클래스를 포함합니다.이 클래스들은 시작 중에 인용됩니다.cs 파일의api로 필요한 주입을 설정합니다.
미국 석유 학회
api는 책과 저자에게 간단한 CRUD 방법을 실행하기 위해 단점이 있는 두 개의 컨트롤러가 있습니다.
dotnetapi-cqrs。api/컨트롤러/BookController대테러 엘리트
using System.Collections.Generic;
using dotnet_api_cqrs.contracts.dto;
using dotnet_api_cqrs.contracts.services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace dotnet_api_cqrs.api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class BookController : ControllerBase
{
private readonly ILogger<BookController> _logger;
private readonly IBookService _bookService;
public BookController(ILogger<BookController> logger, IBookService bookService)
{
_logger = logger;
_bookService = bookService;
}
[HttpGet]
public IEnumerable<Book> Get()
{
return _bookService.GetAllBooks();
}
[HttpGet("{bookID:int}")]
public Book Get(int bookID)
{
return _bookService.GetBook(bookID);
}
[HttpGet("author/{authorID:int}")]
public IEnumerable<Book> GetForAuthor(int authorID)
{
return _bookService.GetBooksForAuthor(authorID);
}
[HttpPost]
public Book Post(Book book)
{
return _bookService.InsertBook(book);
}
[HttpDelete("{bookID:int}")]
public IActionResult Delete(int bookID)
{
_bookService.DeleteBook(bookID);
return Ok();
}
}
}
이익.
이것이 현재 API를 구성하는 기본 설정입니다.나는 너의 생각/문제/비판을 매우 듣고 싶다.부드럽게.이것은 나의 첫 번째 기술 블로그 문장이다.하지만 더 많아요.
Reference
이 문제에 관하여(옷차림이 단정하다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/melodicdevelopment/dapper-cqrs-2ff2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)