理想很豐滿,現實往往很殘酷。
一種按照ddd的方式,根據業務來把自己需要的模組一個一個寫出來,再按照模組把需要的介面一個一個的寫出來,堆砌一些中介軟體,以及解耦的command,handler等等
,一個專案就這麼成型了。上面的專案有一個非常清晰的特點,就是按需開發,不需要去可以定義業務相關的公共的模組,有就有沒就沒。這專案看起來沒有什麼公共框架,就是一個專案。當然這樣效率效能也是最高的,不需要過多的包裝一層又一層的公共程式碼。
有關示例如下,不做過多的贅述:
liuzhixin405/netcore-micro (github.com)
一種業務非常大,開發人員只需要寫業務實現,這就需要一個公共框架,提供公共的可複製模組讓業務人員寫業務程式碼。
下面以為簡潔的方式呈現這種開發模式,專案層級如下:
三個模組分別是業務模組,主機,基礎模組。業務模組Business透過dll形式提供給host來完成註冊和釋出。
主機host可以存放公共的基礎模組,例如註冊、登入、認證等,這裡省略。
業務模組存放業務程式碼,包括提供介面。
流程如下:request => 業務模組controller => business => service=> repository
整個專案介面不變,實現可各異。
在倉儲層定義幾個公共的方法,
public interface IRepository<TEntity,TID> where TEntity : IEntity<TID> { Task<ApiResult> AddAsync(TEntity entity); Task<ApiResult> UpdateAsync(TEntity entity); Task<ApiResult> DeleteAsync(Expression<Func<TEntity, bool>> filter); Task<ApiResult> DeleteAsync(TID id); // 通用分頁查詢 Task<PagedResult<TEntity>> GetPagedAsync(PagingParameters<TEntity> pagingParameters); // 其他常用操作 Task<IEnumerable<TEntity>> FindAsync(Expression<Func<TEntity, bool>> filter); }
服務層也是同樣的方法
[Intercept("business-service log")] public interface IService { Task<ApiResult> AddAsync(IRequestDto requestDto); Task<ApiResult> UpdateAsync(IRequestDto requestDto); Task<ApiResult> DeleteAsyncc(IRequestDto requestDto); Task<ApiResult> GetPagedAsyncc(IRequestDto requestDto) ; Task<ApiResult> FindAsyncc(IRequestDto requestDto); }
依賴注入還是老一套,實現它就行。
public interface IModule { void ConfigureService(IServiceCollection services, IConfiguration configuration = null); void Configure(IApplicationBuilder app, IWebHostEnvironment env = null); } public abstract class ModuleBase : IModule { public virtual void ConfigureService(IServiceCollection services, IConfiguration configuration = null) { } public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env = null) { } }
在主機透過掃描assembly來註冊服務
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Project.Base.Reflect; using System.Reflection; using Project.Base.ProjExtension; using Project.Base.Common; using Project.Base.DependencyInjection; using Project.Base.Module; namespace Project.Host { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); builder.Configuration.AddJsonFile("appsettings.Modules.json", optional: false, reloadOnChange: true); //IModule注入 ,然後掃描呼叫ConfigureService,Business注入需要的服務入口 builder.Services.InitModule(builder.Configuration); var sp = builder.Services.BuildServiceProvider(); var moduleInitializers = sp.GetServices<IModule>(); foreach (var moduleInitializer in moduleInitializers) { moduleInitializer.ConfigureService(builder.Services, builder.Configuration); } // Add services to the container. var assemblys = GolbalConfiguration.Modules.Select(x => x.Assembly).ToList(); var mvcBuilder=builder.Services.AddControllers().ConfigureApplicationPartManager(apm => { var folder = Path.Combine(Directory.GetCurrentDirectory(), "bus_lib"); var serviceList = (builder.Configuration.GetSection("ServiceList").Get<string[]>()) ?? new string[] { "ADM" };//預設載入基礎服務 string[] serviceFiles = Directory.GetFiles(folder, "*.Api.dll").Where(x => serviceList.Any(y => x.Contains(y)) ).ToArray(); foreach (var file in serviceFiles) { if (File.Exists(file)) { var assembly = Assembly.LoadFrom(file); var controllerAssemblyPart = new AssemblyPart(assembly); apm.ApplicationParts.Add(controllerAssemblyPart); } } }); foreach (var assembly in assemblys) { // 掃描並註冊其他程式集中的控制器 mvcBuilder.AddApplicationPart(assembly); // 掃描並註冊其他程式集中的服務 針對特性注入 builder.Services.ReisterServiceFromAssembly(assembly); } // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddBusinessServices(); var app = builder.Build(); ServiceLocator.Instance = app.Services; //imodule 的Configure呼叫,business可以實現中介軟體等操作 foreach (var moduleInitializer in moduleInitializers) { moduleInitializer.Configure(app, app.Environment); } // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
業務需求注入程式碼如下:
using ADM001_User.Model; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.Serialization; using MongoDB.Bson; using MongoDB.Driver; using Project.Base.IRepository; using Project.Base.Module; using Project.Base.Reflect; using Project.Base.Repository; using Project.Base.Services; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ADM001_User.Business.Settings; using Project.Base.Model; namespace ADM001_User.Business { public class UserModule : ModuleBase { public override void ConfigureService(IServiceCollection services, IConfiguration configuration = null) { services.AddDbContext<UserDbContext>(options => options.UseInMemoryDatabase("InMemoryDb")); services.AddScoped<IRepository<User, int>, GenericRepository<User, int, UserDbContext>>(); services.AddTransient<IService, UserService>(); AddMongo(services); AddMongoRepository<User, int>(services, "users"); } private static IServiceCollection AddMongo(IServiceCollection services) { BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); BsonSerializer.RegisterSerializer(new DateTimeOffsetSerializer(BsonType.String)); services.AddSingleton(serviceProvider => { var configuration = serviceProvider.GetService<IConfiguration>(); var serviceSettings = configuration.GetSection(nameof(ServiceSettings)).Get<ServiceSettings>(); var mongoDbSettings = configuration.GetSection(nameof(MongoDbSettings)).Get<MongoDbSettings>(); var mongoClient = new MongoClient(mongoDbSettings.ConenctionString); return mongoClient.GetDatabase(serviceSettings.ServiceName); }); return services; } private static IServiceCollection AddMongoRepository<T, TID>(IServiceCollection services, string collectionName) where T : IEntity<TID> { services.AddSingleton<IRepository<User, int>>(serviceProvider => { var db = serviceProvider.GetService<IMongoDatabase>(); return new MongoRepository<User, int>(db, "collectionname"); }); return services; } } }
在business層加了aop,透過proxy的方式
using Castle.DynamicProxy; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace Project.Base.Reflect { public static class ServiceExtension { private static readonly ProxyGenerator _generator = new ProxyGenerator(); public static IServiceCollection AddBusinessServices(this IServiceCollection services) { var folder = Path.Combine(Directory.GetCurrentDirectory(), "bus_lib"); var dllFiles = Directory.GetFiles(folder, "*.Business.dll"); var assemblies = dllFiles.Select(Assembly.LoadFrom).ToArray(); var businessTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => t.IsClass&&!t.IsAbstract)).Where(type => type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IBusiness<>))).ToList(); CastleInterceptor castleInterceptor = new CastleInterceptor(); foreach (var type in businessTypes) { var interfaceType = type.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IBusiness<>)); services.AddTransient(interfaceType, provider => { var target = ActivatorUtilities.CreateInstance(provider, type); return _generator.CreateInterfaceProxyWithTarget(interfaceType, target, castleInterceptor); }); } return services; } } }
在你需要的每個方法前加上特性就可以了
using Project.Base.Model; using Project.Base.ProjAttribute; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ADM001_User.Business { /// <summary> /// 有需要就實現前後動作 /// </summary> public class AddAop: BaseAopAttribute { public override Task After(BusinessAopContext aopContext) { return Task.CompletedTask; } public override Task Before(BusinessAopContext aopContext) { return Task.CompletedTask; } } }
再控制器層加了個公共的,不管是controller攔截還是公共的部分都可以寫到這裡
[Route("api/[controller]/[action]")] [ApiController] public class InitController<TModel>:ControllerBase { protected readonly ILogger<InitController<TModel>> _logger; public InitController(ILogger<InitController<TModel>> logger) { _logger = logger; } }
該框架主打就是一個簡陋,像日誌,快取 ,訊息中介軟體都可以提前約定好公共介面,service層介面呼叫,business層注入需要的實現。按照介面和實現分離的方式該專案還需要調整下目錄
地址如下:
liuzhixin405/single-arch (github.com)