DDD 异步方法、读写分离、数据库日志完善、用户信息
使用 .NET Core 从零开始写一个 DDD 领域模型的框架
每一篇文章打一个 tag
这版代码
异步方法
读写分离
数据库日志
全局登陆信息
数据库日志完善
public class AutofacFilter : IInterceptor { public void Intercept(IInvocation invocation) { DateTime startTime = DateTime.Now; //objs 是当前拦截方法的入参 object[] objs = invocation.Arguments; invocation.Proceed(); // ret 是当前方法的返回值 object ret = invocation.ReturnValue; DateTime endTime = DateTime.Now; if (invocation.Method.CustomAttributes?.FirstOrDefault(i => i.AttributeType.Name == "AOPLogAttribute") != null) { if (invocation.Method.Name == "FindAsync") { var taskType = ret.GetType(); IBaseDomain domain = (IBaseDomain)taskType.GetProperty("Result").GetValue(ret);
// 这两行为什么要这么写,在异步方法哪里解释
BaseOrmModel model = domain.GetModel(); DBLogSeleteModel logModel = new DBLogSeleteModel() { CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), StartTimer = startTime, EndTimer = endTime, TakeTime = (endTime - startTime).TotalMilliseconds.ToString(), ReturnValue = JsonConvert.SerializeObject(model), OperatorKeyId = AuthorizationUtil.GetOperatorKeyId(), OperatorUserkeyId = AuthorizationUtil.GetCurrentUserKeyId(), OperatorUserName = AuthorizationUtil.GetCurrentUserName(), }; DBLog.WriteLog(logModel); } } } }
上个版本已经有了 修改和新增的日志了,这回把 查询的给补上。(删除是逻辑删除,包含在修改里面了)
异步方法
IDomain Find(Guid keyId, bool readOnly = false); Task<IBaseDomain> FindAsync(Guid keyId, bool readOnly = false); IList<IDomain> FindList(Expression<Func<OrmEntity, bool>> predicate, bool readOnly = false); Task<IList<IDomain>> FindListAsync(Expression<Func<OrmEntity, bool>> predicate, bool readOnly = false);
仓储层,在原有方法的基础上,添加异步方法
public async Task<bool> UpdateUser(UserRequestDto request) { IUserDomain userDomain = UserRepository.Find(request.KeyId); userDomain.SetUserName(request.UserName); userDomain.SetPassWord(request.PassWord); userDomain.SetLastLoginTime(DateTime.Now); return await SaveChangesAsync() > 0; }
public async Task<UserResponseDto> Find(UserRequestDto request) { IBaseDomain baseDomain = await UserRepository.FindAsync(request.KeyId); IUserDomain userDomain = (IUserDomain)baseDomain; UserResponseDto respDto = new UserResponseDto() { KeyId = userDomain.GetKeyId(), CreateUserKeyId = userDomain.GetCreateUserKeyId(), UpdateUserKeyId = userDomain.GetUpdateUserKeyId(), 。。。 }; return respDto; }
业务层,应用层,表现层全部统一使用异步方法
异步方法并不能减少单个请求响应的时间。(FindAsync、SaveChangeAsync、ToListAsync 等异步方法并不会节省时间)
但是可以提高整体的并发量,可以用更少的线程完成更多的响应。
但是异步方法也带来了一些很麻烦的问题。
问题一:逆变与协变
在 AutofacFilter 方法拦截器中对异步方法 FindAsync 进行拦截的时候出现了逆变协变的问题
object ret = invocation.ReturnValue; // FindAsync 执行完毕后的返回值类型为 Task<IUserDomain>
Task<IBaseDomain> domain = (Task<IBaseDomain>)ret; // 在执行的时候是会抛异常的。
由于 Task 中没有提供逆变与协变的重载所以Task<子类> 子类 = Task<父类> 是不可以的
解决方式:通过反射直接调用 Task 的 Result 方法。把 Result 强制转换成 IBaseDomain 就行了
问题二:不能任何情况下都使用异步方法
SaveChangeAsync 与 FindAsync 方法互斥
一个 ORM对象 使用 FindAsync 获取,就只能用 SaveChange 方法保存
一个 ORM对象 使用 SaveChangeAsync 方法保存,就只能用 Find 获取
读写分离
interface IUnitOfWork
interface IAppUnitOfWork : IUnitOfWork
interface IReadUnitOfWork : IUnitOfWork
class AppUnitOfWork : IAppUnitOfWork
class ReadUnitOfWork : IReadUnitOfWork
定义了一个读写的工作单元,一个只读的工作单元
通过 AppUnitOfWorkFactory 创建指定的工作单元
仓储层中所有的方法都要添加一个 bool readOnly = false 参数,用来控制程序使用那个数据库
这个Demo中读库和写库链接的是同一个数据库,但是该有的东西还是要弄一下,后续看情况弄一套真实的读库和写库
只读的 UnitOfWork 不提供修改数据库的方法
在业务层、领域层 手动确定是从读库拿数据还是从写库拿数据
登陆信息线程内唯一
通过Action执行前的Filter把Cookie中的用户信息写入到当前请求的线程中
单元测试中直接把用户登陆信息写死到线程中
异步方法切换线程的时候会把当前线程中的信息复制到切换的线程中