OnionArch

2022-10-11 18:04:08

博主最近失業在家,找工作之餘,自己動手寫了個洋蔥架構(整潔架構)解決方案,以總結和整理以前的專案經驗,起名叫OnionArch,其目的是為了更好的實現採用DDD(領域驅動分析)和命令查詢職責分離(CQRS)的洋蔥架構。

什麼是OnionArch

OnionArch解決方案清晰的展示了程式各分層的職責,幫助程式設計師寫出邏輯更清晰的程式碼,提高程式的高內聚、低耦合性。提高程式邏輯的可重用性,可延伸性和可測試性。

OnionArch可降低單個微服務,特別是SAAS微服務開發的複雜度,在保證軟體質量的基礎上提高軟體開發效率。

OnionArch基於最新的開源.Net 7.0 RC1, 資料庫採用PostgreSQL, 目前實現了包括多租戶在內的12個特性。詳細內容請看:https://www.cnblogs.com/xiaozhuang/p/16772485.html

OnionArch如何實現更新指定欄位的通用Handler

本篇主要講述OnionArch如何實現更新指定欄位的通用Handler,這裡的Handler是指MediatR中繼承IRequestHandler介面的處理邏輯。

Let me show you the code:

 public async override Task<UpdateProductReply> UpdateProduct(UpdateProductRequest request, ServerCallContext context)
        {
            UpdateCommand<UpdateProductRequest> updateProductCommand = new UpdateCommand<UpdateProductRequest>(Guid.Parse(request.ProductId),request,p => p.Description);
            await _mediator.Send(updateProductCommand);
            return new UpdateProductReply()
            {
                Message = "Update Product sucess"
            };
        }

這是一個實現UpdateProduct業務用例的GRPC方法,新建一個UpdateCommand更新命令,並傳入ProductId和要更新的資料request(表單提交的ViewModel),以及要更新的屬性欄位。然後呼叫Mediator.Send傳送該命令即可。

UpdateCommand命令定義如下,繼承自CQRS的ICommand介面,並採用了C#的record型別,確保必填欄位不為空。

  public record UpdateCommand<TModel>(Guid Id, TModel Model, params Expression<Func<TModel, object>>[] UpdateProperties) : ICommand
    { }

第三個引數可以傳入多個要更新的欄位的Lambda 表示式,不採用字串List的方式以防止程式設計師寫錯。

Mediator的更新命令處理物件UpdateCommandHandler 接收到該命令進行處理:

 public class UpdateCommandHandler<TModel, TEntity> : IRequestHandler<UpdateCommand<TModel>> where TEntity : BaseEntity
    {
        private readonly CURDDomainService<TEntity> _curdDomainService;

        public UpdateCommandHandler(CURDDomainService<TEntity> curdDomainService)
        {
            _curdDomainService = curdDomainService;
        }

        public async Task<Unit> Handle(UpdateCommand<TModel> request, CancellationToken cancellationToken)
        {
            TEntity updateEntity = await _curdDomainService.Retrieve(request.Id);
            updateEntity = request.Model.Adapt(updateEntity);
            List<string> updateProperties = new List<string>();
            foreach (var expression in request.UpdateProperties)
            {
                var member = expression.Body as MemberExpression;
                if (member != null)
                    updateProperties.Add(member.Member.Name);
            }
            await _curdDomainService.Update(updateEntity, updateProperties);
            return Unit.Value;
        }
    }

首先根據傳入的ProductId獲取到Product實體,然和將傳入的request Model資料,通過Mapster的Adapt方法賦值給該實體,該實體欄位就有了最新的值。

然後將傳入的要更新的屬性欄位Lambda 表示式轉換為屬性字串List,再呼叫領域層方法儲存該實體到資料庫。

領域層更新實體的倉儲方法如下:

  public async Task<TEntity> Update(TEntity entity, IEnumerable<string> updateProperties)
        {
            var entry = _dbContext.ChangeTracker.Entries<TEntity>().FirstOrDefault(p => p.Entity == entity);
            if (entry == null)
            {
                entry = _dbContext.Set<TEntity>().Attach(entity);
            }
            entry.State = EntityState.Unchanged;
            foreach (var updateProperty in updateProperties)
            {
                entry.Property(updateProperty).IsModified = true;
            }
            return entry.Entity;
        }

可以看到,該方法只將要更新的欄位設定為IsModified = true,即可達到更新特定欄位的目的。

輸出的SQL語句如下:

Executed DbCommand (11ms) [Parameters=[@p3='?' (DbType = Guid), @p0='?', @p1='?' (DbType = DateTime), @p2='?'], CommandType='Text', CommandTimeout='30']
      UPDATE "T_Product" SET "Description" = @p0, "LastModified" = @p1, "LastModifiedBy" = @p2
      WHERE "Id" = @p3;

可以看到,SQL只更新了Description欄位,LastModified和LastModifiedBy欄位是OnionArch的實體資料審計特性自動加上的。打完收工。

找工作時間

接下來又到了找工作廣告時間:

▪ 博主有15年以上的軟體技術實施經驗(Technical Leader),專注於微服務和雲原生(K8s)軟體架構設計、專注於 .Net Core\Java開發和Devops構建釋出。
▪ 博主10年以上的軟體交付管理經驗(Project Manager & Product Ower),致力於敏捷(Scrum)專案管理、軟體產品業務需求分析和原型設計。
▪ 博主熟練設定和使用 Microsoft Azure雲。
▪ 博主為人誠懇,積極樂觀,工作認真負責。 

我家在廣州,也可以去深圳工作。做架構和專案管理都可以,希望能從事穩定行業的業務數位化轉型。有工作機會推薦的朋友可以加我微信 15920128707,微信名字叫Jerry。