工作有些年头了一直在搬砖,下定决心从零开始写一套领域模型的项目
把知道的东西变成会的,把会的东西融会贯通
最终能不能用无所谓,总要留点什么东西
Github 仓库地址
每一篇文章对应一个 tag
这版代码定义了
领域模型中需要那些层
层与层之间的基本引用
使用.NET Core 内置的依赖注入框架,对各层之间进行解耦 (后面会替换成 Autofac)
以下内容都只是我本人的理解,可能对也可能不对
由于项目之间只引用了接口没有引用实现,所以直接编译后的DLL只有接口没有实现
因此:
一、各层编译后的DLL,都输出到同一个目录下
二、Web项目,编译的时候把该项目中所有的DLL复制到 bin 文件中对应的路径下
项目结构
0-Infrastructure
Core2022.Enum
Core2022.Framework.Commons 定义一些公共方法
Core2022.Framework 定义Domaim、Repository、UnitOfWork 基本实现,IoC、Middleware等实现
1-Presentation
Core2022.API
Core2022.Web
2-Application
Core2022.Application.Services
Core2022.Application.Services.DTO 表现层与应用层之间交互用的对象
Core2022.Application.Services.Interface
3-Domain
Core2022.Domain
Core2022.Domain.Model ORM对象
Core2022.Domain.Interface
4-Repository
Core2022.Repository
Core2022.Repository.Interface
概念:
表现层:负责向用户显示或者收集用户输入的指令。(Web、API、WCF 等)
应用层:对应一个具体的业务,指挥领域对象来解决问题。
领域层:负责具体的业务概念、状态、规则等信息。
基础设施层:为各层提供支持,为表现层提供中间件等、为应用层提供信息的传递、为领域层提供数据的持久化
仓储层:某个领域独有的方法(获取修改数据的方法),放到该领域的仓储层中。
领域对象与ORM对象
ORM对象,只是一个用来和数据库表字段映射用的类型,他只能包含一些简单的属性(姓名、年龄等)
领域对象,ORM对象只是领域对象的一个属性(私有属性),领域对象通过 Get,Set方法访问或者修改ORM对象。
GetName() { return ORM对象.Name }
SetName(string name) { ORM对象.Name = name }
领域对象还包含其他属性(所在部门、直属领导)。
UserDomain.GetDept() { return DeptDomain } 获取员工所在的部门
UserDomain.GetLeader() { return UserDomain } 获取员工的直属领导
UserDomain.GetCustome(Guid userKeyId) { return List<CustomerDoamin> } 获取该员工的客户列表
领域对象还包含了对象的行为、规则(发送消息)。
UserDomain.SendInformation(string msg) { ... } 发送消息行为
UserDomain.AddCustome(xxx) { 员工增加100块收入,部门任务整体奖金增加 } 增加客户规则
在快速开发的时候,往往为了效率经常是怎么简单怎么来。因此业务逻辑会分散到表现层,应用层,数据层,数据库脚本,导致的后果就是后续分析问题的时候异常的困难。
领域模型的高内聚、合理的分层可以解决这些问题。
但是领域模型也有他自己的问题
--select * from
UPDATE t1 set t1.UserName = 'XXXX' from
[user] as t1
inner join Dept as t2 on t1.KeyId = t2.UserKeyId
where t1.KeyId = 'F4095679-83B6-4646-9BFA-7745D7CD3AD0'
比如我要修改某个员工他所在部门的名字,一条SQL就能完成,但是在领域模型中需要先来个 UserDomain 在通过 UserDomain 获取到 DeptDomain 在调用 DeptDomain.SetDeptName() 修改这个部门名字
再比如 UserDomain.GetCustome() 获取到一个 List<CustomerDoamin> 不管业务是什么,只要用到客户列表就会执行 Select [ORM对象中的所有字段] from Customer where UserKeyId = 'XXXXXX'。
引用关系
0-Infrastructure
Core2022.Enum
Core2022.Framework.Commons
Core2022.Framework
1-Presentation
Core2022.API
Core2022.Web
Core2022.Application.Services.DTO
Core2022.Application.Services.Interface
2-Application
Core2022.Application.Services
Core2022.Application.Services.DTO
Core2022.Application.Services.Interface
Core2022.Domain.Model
Core2022.Domain.Interface
Core2022.Repository.Interface
Core2022.Application.Services.DTO
Core2022.Application.Services.Interface
Core2022.Application.Services.DTO
3-Domain
Core2022.Domain
Core2022.Domain.Model
Core2022.Domain.Interface
Core2022.Domain.Model
Core2022.Domain.Interface
4-Repository
Core2022.Repository
Core2022.Domain.Model
Core2022.Domain.Interface
Core2022.Repository.Interface
Core2022.Repository.Interface
Core2022.Domain.Model
Core2022.Domain.Interface
依赖注入
在 Startup 中配置依赖注入,实现各层之间的解耦
InjectionServicesExtension.cs using Core2022.Framework.Attributes; using Core2022.Framework.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Reflection; namespace Core2022.Framework.Commons.Injections { /// <summary> /// /// </summary> public static class InjectionServicesExtension { /// <summary> /// 通过反射注册项目中的所有服务 /// </summary> /// <param name="services"></param> /// <param name="configuration"></param> /// <returns></returns> public static void InjectionServices(this IServiceCollection services, IConfiguration configuration) { foreach (var assemblyString in AppSettings.InjectionServices.AssemblyStrings) { var serviceTypes = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + assemblyString).GetTypes(); if (serviceTypes != null && serviceTypes.Length > 0) { foreach (var service in serviceTypes) { var attribute = service.GetCustomAttribute(typeof(InjectionAttribute), false); if (attribute is InjectionAttribute) { InjectionAttribute injectionAttribute = attribute as InjectionAttribute; var serviceInterfaceName = injectionAttribute.ServiceInterfaceName; var serviceLifetime = injectionAttribute.ServiceLifetime; AddInjectionWithLifetime(services, serviceLifetime, serviceInterfaceName, service); } } } } } /// <summary> /// 向依赖框架中注册服务 /// </summary> /// <param name="services">依赖框架</param> /// <param name="serviceLifetime">服务生命周期</param> /// <param name="service">服务</param> /// <param name="implementation">服务的实现</param> /// <returns></returns> private static IServiceCollection AddInjectionWithLifetime(IServiceCollection services, ServiceLifetime serviceLifetime, Type service, Type implementation) { switch (serviceLifetime) { case ServiceLifetime.Scoped: return services.AddScoped(service, implementation); case ServiceLifetime.Singleton: return services.AddSingleton(service, implementation); case ServiceLifetime.Transient: return services.AddTransient(service, implementation); default: return services; } } } }
InjectionAttribute.cs using Microsoft.Extensions.DependencyInjection; using System; namespace Core2022.Framework.Attributes { public class InjectionAttribute : Attribute { /// <summary> /// 服务依赖的接口 /// </summary> public Type ServiceInterfaceName { get; set; } /// <summary> /// 注册服务的生命周期 /// </summary> public ServiceLifetime ServiceLifetime { get; set; } /// <summary> /// 依赖特性 /// </summary> /// <param name="name">服务依赖接口</param> /// <param name="serviceLifetime">服务生命周期</param> public InjectionAttribute(Type serviceInterfaceName, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped) { ServiceInterfaceName = serviceInterfaceName; ServiceLifetime = serviceLifetime; } } }
Startup.ConfigureServices public void ConfigureServices(IServiceCollection services) { services.InjectionInjectionOrmModel(); //注册控制器和视图 services.AddControllersWithViews(); //注册服务 services.InjectionServices(Configuration); }
appsettings.json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", /*注册服务 配置*/ "InjectionServices": { "AssemblyStrings": [ "Core2022.Domain.Model.dll", "Core2022.Application.Services.dll", "Core2022.Domain.dll", "Core2022.Repository.dll" ] } }
这一版代码都属于基础知识,都是固定用法
唯一麻烦的就是编译项目的时候需要编译整个解决方案