理想很丰满,现实往往很残酷。
一种按照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)