本文转载自微信公众号“老王加”,老王加作者老王。转载本文请联系老王Plus公众号。1.洋葱架构简介洋葱架构实际上已经存在了一段时间。2017年下半年左右,会有相关的说法。但是大量的文章都停留在理论讨论上,今天我们就用一个项目来完成这个架构。洋葱架构,有时称为干净架构,存在于高质量软件中。与其他架构相比,洋葱架构具有更好的可测试性、实用性和稳定性,并且足够灵活,可以充分适应项目未来可能的增长和演进。可以说洋葱架构完美解决了三层或N层架构所面临的困难和问题。吹牛说完了,我们来看一张图:这张图充分说明了为什么叫洋葱架构。然而,这不是重点。这种架构最重要的是里面的代码依赖原则:从外到内,只有一个方向。内循环中的代码不应该知道外循环的任何信息。从上图可以看出,洋葱架构也使用了层的概念。不过,它与我们习惯的三层或N层不同。我们来看看每一层:数据层(DomainLayer)存在于架构的中心部分,由所有业务数据实体组成。在大多数情况下,这是我们的数据模型。在下面的练习代码中,我使用EF(EntityFramework)来操作数据库。存储层(RepositoryLayer)存储层在架构中充当连接服务层和数据模型的纽带,在这一层将维护所有数据库操作和应用数据的上下文。通常的做法是一个接口,用来描述数据访问中涉及的读写操作。服务层(ServicesLayer)服务层用于实现存储层与项目的通信,同时也可以保存实体的业务逻辑。在这一层,服务接口与实现分离,实现解耦和焦点分离。用户界面层(UILayer)就不解释了。项目的最终外层。请注意,这可能是网站或API。无需担心是否有实际接口。在我们的实际代码中,我使用API。二、好好练,现在直接上代码。1.创建项目没有解释,都是套路:%dotnetnewwebapi-odemo-fnetcoreapp3.1我的项目用的是DotnetCore3.1。框架不重要,基本上什么版本都可以。下面设置Swagger是我的习惯,而这个项目是一个WebApi,所以安装一个Swagger很方便。%dotnetaddpackageswashbuckle.aspnetcoreSwagger的设置不是本文重点,略过。需要的同学可以看看源码。接下来我们在项目中建立三个目录:DomainLayerRepositoryLayerServicesLayer这三个目录分别对应上面的三层。在这个项目中,UI其实就是已经存在的控制器Controller。建立这三个目录的目的是为了放置三层的代码。后面编码的时候,你会看到这三层之间的关系。另外,这三层在实际应用中可以独立作为三个类库使用,会更加清晰。前面说过,我会用EF来操作数据库。所以这里需要引入三个库:%dotnetaddpackageMicrosoft.EntityFrameworkCore%dotnetaddpackageMicrosoft.EntityFrameworkCore.Relational%dotnetaddpackagePomelo.EntityFrameworkCore.MySql注意微软的EF框架没有提供MySQL访问,所以引用了第三方库。至此,项目的准备工作就完成了。2.实现数据层在DomainLayer目录下,创建一个Models目录。在Models目录中,创建两个类:BaseEntity.cspublicclassBaseEntity{publicintId{get;set;}publicDateTimeCreatedDate{get;set;}publicDateTimeModifiedDate{get;set;}publicboolIsActive{get;set;}}Customer.cspublicclassCustomer:BaseEntity{publicstringCustomerName{get;set;}publicstringPurchasesProduct{get;set;}publicstringPaymentType{get;set;}}两个类,Customer派生自BaseEntity。没有什么特别的意思,也是一种习惯。而且以后写入存储层也方便。稍后,我们将使用Customer和BaseEntity实体类创建的数据表。为了让大家看的清楚,我这里新建一个目录EntityMapper,在该目录下写一个表结构映射。CustomerMap.cspublicclassCustomerMap:IEntityTypeConfiguration{publicvoidConfigure(EntityTypeBuilderbuilder){builder.HasKey(x=>x.Id).HasName("pk_customerid");builder.Property(x=>x.Id).ValueGeneratedOnAdd().HasColumnName("id").HasColumnType("INT");builder.Property(x=>x.CustomerName).HasColumnName("customer_name").HasColumnType("NVARCHAR(100)");builder.Property(x=>x.PurchasesProduct).HasColumnName("purchased_product").HasColumnType("NVARCHAR(100)").IsRequired();builder.Property(x=>x.PaymentType).HasColumnName("payment_type").HasColumnType("NVARCHAR(50)").IsRequired();builder.Property(x=>x.CreatedDate).HasColumnName("created_date").HasColumnType("datetime");builder.Property(x=>x.ModifiedDate)。HasColumnName("modified_date").HasColumnType("datetime");builder.Property(x=>x.IsActive).HasColumnName("is_active").HasColumnType("bit");}}也可以自己创建表ef.Customer:CREATETABLE`Customer`(`id`intNOTNULLAUTO_INCREMENT,`created_date`datetimeDEFAULTNULL,`customer_name`varchar(255)CHARACTERSETutf8mb4COLLATEutf8mb4_0900_ai_ciDEFAULTNULL,`is_active`bit(1)DEFAULTNULL,`modified_date`datetimeDEFAULTNULL,`payment_type`varchar(50)DEFAULTNULL,`purchased_product`varchar(100)DEFAULTNULL,PRIMARYKEY(`id`)USINGBTREE)3.实际存储层主要是用来操作数据库的。首先在Startup.cs中配置数据库引用:publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){services.AddDbContextPool(options=>options.UseMySql("server=192.168.0.241;user=root;password=xxxxxx;database=ef",newMySqlServerVersion(newVersion(8,0,21)),mysqlOptions=>{mysqlOptions.CharSetBehavior(CharSetBehavior.NeverAppend);}));}}这里偷懒了,连接字符串直接写代码。正式做项目的时候,最好写在配置文件里。在RepositoryLayer目录下创建一个DataContext,用于放置相关的数据库会话和操作实例:base.OnModelCreating(modelBuilder);}}创建目录RespositoryPattern存放数据库操作类。按照注册的原则,会是两个文件,一个接口定义,一个现实类:IRepository.cspublicinterfaceIRepositorywhereT:BaseEntity{IEnumerableGetAll();TGet(intId);voidInsert(Tentity);voidUpdate(Tentity);voidDelete(Tentity);voidRemove(Tentity);voidSaveChanges();}Repository.cspublicclassRepository:IRepositorywhereT:BaseEntity{privatereadonlyApplicationDbContext_applicationDbContext;privateDbSetentities;publicRepository(ApplicationDbContextapplicationDbContext){_applicationDbContext=applicationDbContext;实体=_applicationDbContext.Set();}publicvoidDelete(Tentity){if(entity==null){thrownewArgumentNullException("entity");}entities.Remove(entity);_applicationDbContext.SaveChanges();}publicTGet(intId){returnentities.SingleOrDefault(c=>c.Id==Id);}publicIEnumerableGetAll(){returnentities.AsEnumerable();}publicvoidInsert(Tentity){if(entity==null){thrownewArgumentNullException("entity");}entities.Add(entity);_applicationDbContext.SaveChanges();}publicvoidRemove(Tentity){if(entity==null){thrownewArgumentNullException("entity");}entities.Remove(entity);}publicvoidSaveChanges(){_applicationDbContext.SaveChanges();}publicvoidUpdate(实体){if(entity==null){thrownewArgumentNullException("entity");}entities.Update(entity);_applicationDbContext.SaveChanges();}}4.实现服务层服务层用于实现核心业务逻辑同样先建一个目录CustomerService,方法方便注册,也是一个接口一个类:ICustomerService.cspublicinterfaceICustomerService{IEnumerableGetAllCustomers();CustomerGetCustomer(intid);voidInsertCustomer(Customercustomer);voidUpdateCustomer(Customercustomer);Customercustomer(voidUpdateCustomer);Customercustomer(void).cspublicclassCustomerService:ICustomerService{privateIRepository_repository;publicCustomerService(IRepositoryrepository){_repository=repository;}publicIEnumerableGetAllCustomers(){return_repository.GetAll();}publicCustomerGetCustomer(intid){return_repository.Get(id));}publicvoidInsertCustomer(Customercustomer){_repository.Insert(customer);}publicvoidUpdateCustomer(Customercustomer){_repository.Update(customer);}publicvoidDeleteCustomer(intid){Customercustomer=GetCustomer(id);_repository.Remove(customer);_repository.SaveChanges();}}4。注入这孩子就是套路了,不解释。publicvoidConfigureServices(IServiceCollectionservices){services.AddScoped(typeof(IRepository<>),typeof(Repository<>));services.AddTransient();}5.控制器的三个重要层已经实现。下面做一个演示用的控制器:CustomerController.cs[ApiController][Route([controller]")]publicclassCustomerController:ControllerBase{privatereadonlyICustomerService_customerService;publicCustomerController(ICustomerServicecustomerService){_customerService=customerService;}[HttpGet(nameof(GetCustomer))]publicIActionResultGetCustomer(intid){varresult=_customerService.GetCustomer(id);if(result!=null){returnOk(result);}returnBadRequest("Norecordsfound");}[HttpGet(nameof(GetAllCustomer))]publicIActionResultGetAllCustomer(){varresult=_customerService.GetAllCustomers();if(result!=null){returnOk(result);}returnBadRequest("Norecordsfound");}[HttpPost(nameof(InsertCustomer))]publicIActionResultInsertCustomer(Customercustomer){_customerService.InsertCustomer(客户);returnOk("Datainserted");}[HttpPut(nameof(UpdateCustomer))]publicIActionResultUpdateCustomer(Customercustomer){_customerService.UpdateCustomer(客户);returnOk("Updationdone");}[HttpDelete(nameof(DeleteCustomer))]publicIActionResultDeleteCustomer(intId){_customerService.DeleteCustomer(Id);returnOk("DataDeleted");}}代码部分全部编译运行~~~三、总结通过上面的代码可以看出:洋葱架构的各个层之间通过接口相互关联,数据导入是在运行时进行的,应用是基于区域模型的,所有的外部依赖,比如数据集访问和管理调整,都在外部处理。适应性强,设计方便。总之,从应用的角度来看,洋葱架构被认为是一个非常好的架构。在我的经??验中,它具有比较大的优势在多个共同开发的项目中,本文相关代码在https://github.com/humornif/Demo-Code/tree/master/0045/demo