我的第一个微服务系列(九):使用领域驱动创建Project项目

一、领域驱动设计DDD

  因为在之前的工作中也未曾涉及到DDD,所以到最近才开始学习。看过《实现领域驱动设计》这本书,说实话,看完还是一知半解,在实际项目中怎么应用,也许需要锤炼才能逐渐明白。还好,在博客园里面找到了比较有深度的阐述DDD的文章进行学习,感谢。

  关于DDD的限界上下文,实体、聚合、值对象、领域服务、仓储等概念这里就不说了。个人觉得DDD最重要的还是在对业务需求的把控上,只有深入了解了业务,才能更好的界定上下文。

  DDD中认为涉及到操作数据库的需要通过聚合根来实现,其他的不应该直接的操作数据库。

  关于DDD更多的知识可以参考:

  Jesse的关于DDD的博文: https://www.cnblogs.com/jesse2013/category/610362.html 

  dax.net 关于DDD的博文: https://www.cnblogs.com/daxnet/archive/2010/11/02/1867392.html

二、CQRS命令查询职责分离 

  CQRS本身只是一个读写分离的架构思想,全称是:Command Query Responsibility Segregation,即命令查询职责分离,表示在架构层面,将一个系统分为写入(命令)和查询两部分。一个命令表示一种意图,表示命令系统做什么修改,命令的执行结果通常不需要返回;一个查询表示向系统查询数据并返回。

  CQRS架构中,另外一个重要的概念就是事件,事件表示命令操作领域中的聚合根,然后聚合根的状态发生变化后产生的事件。

  在我们的本项目中将使用MediatR来实现CQRS。

  关于CQRS的更多内容可以学习汤雪华的博客:https://www.cnblogs.com/netfocus/

三、MediraR

  什么是MediatR呢?我们看下其 Github 上的介绍。

  .NET中的简单中介者模式实现,一种进程内消息传递机制(无其他外部依赖)。 支持以同步或异步的形式进行请求/响应,命令,查询,通知和事件的消息传递,并通过C#泛型支持消息的智能调度。

  其核心是一个中介者模式的.NET实现,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。

  关于其使用可以参考:https://github.com/jbogard/MediatR/wiki

四、项目分层

  我们将项目分为Project.Domain、Project.Infrastructure、Project.Api。经典的DDD分层为四层,领域层、基础设施层、应用层、展现层。这里我们把应用层和Project.Api放到一起。

  1、实现领域层Domain。定义实体,聚合根,值对象。在实体中我们可以添加或移除领域事件。

using System;
using MediatR;
using System.Collections.Generic;

namespace Project.Domain.Seedwork
{
    /// <summary>
    /// Description: Entity
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:36:46
    /// </summary>
    public abstract class Entity
    {
        int? _requestedHashCode;
        int _Id;        
        public virtual  int Id 
        {
            get
            {
                return _Id;
            }
            protected set
            {
                _Id = value;
            }
        }

        private List<INotification> _domainEvents;
        public IReadOnlyCollection<INotification> DomainEvents => _domainEvents?.AsReadOnly();

        public void AddDomainEvent(INotification eventItem)
        {
            _domainEvents = _domainEvents ?? new List<INotification>();
            _domainEvents.Add(eventItem);
        }

        public void RemoveDomainEvent(INotification eventItem)
        {
            _domainEvents?.Remove(eventItem);
        }

        public void ClearDomainEvents()
        {
            _domainEvents?.Clear();
        }

        public bool IsTransient()
        {
            return this.Id == default(Int32);
        }

        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Entity))
                return false;

            if (Object.ReferenceEquals(this, obj))
                return true;

            if (this.GetType() != obj.GetType())
                return false;

            Entity item = (Entity)obj;

            if (item.IsTransient() || this.IsTransient())
                return false;
            else
                return item.Id == this.Id;
        }

        public override int GetHashCode()
        {
            if (!IsTransient())
            {
                if (!_requestedHashCode.HasValue)
                    _requestedHashCode = this.Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)

                return _requestedHashCode.Value;
            }
            else
                return base.GetHashCode();

        }
        public static bool operator ==(Entity left, Entity right)
        {
            if (Object.Equals(left, null))
                return (Object.Equals(right, null)) ? true : false;
            else
                return left.Equals(right);
        }

        public static bool operator !=(Entity left, Entity right)
        {
            return !(left == right);
        }
    }
}
namespace Project.Domain.Seedwork
{
    /// <summary>
    /// Description: IAggregateRoot
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:36:46
    /// </summary>
    public interface IAggregateRoot { }

}
using System.Collections.Generic;
using System.Linq;

namespace Project.Domain.Seedwork
{
    /// <summary>
    /// Description: ValueObject
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:36:46
    /// </summary>
    public abstract class ValueObject
    {
        protected static bool EqualOperator(ValueObject left, ValueObject right)
        {
            if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
            {
                return false;
            }
            return ReferenceEquals(left, null) || left.Equals(right);
        }

        protected static bool NotEqualOperator(ValueObject left, ValueObject right)
        {
            return !(EqualOperator(left, right));
        }

        protected abstract IEnumerable<object> GetAtomicValues();

        public override bool Equals(object obj)
        {
            if (obj == null || obj.GetType() != GetType())
            {
                return false;
            }
            ValueObject other = (ValueObject)obj;
            IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
            IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
            while (thisValues.MoveNext() && otherValues.MoveNext())
            {
                if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null))
                {
                    return false;
                }
                if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current))
                {
                    return false;
                }
            }
            return !thisValues.MoveNext() && !otherValues.MoveNext();
        }

        public override int GetHashCode()
        {
            return GetAtomicValues()
             .Select(x => x != null ? x.GetHashCode() : 0)
             .Aggregate((x, y) => x ^ y);
        }

        public ValueObject GetCopy()
        {
            return this.MemberwiseClone() as ValueObject;
        }
    }
}

  定义仓储接口

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Project.Domain.Seedwork
{
    /// <summary>
    /// Description: IUnitOfWork
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:36:46
    /// </summary>
    public interface IUnitOfWork : IDisposable
    {        
        Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
        Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken));
    }
}
namespace Project.Domain.Seedwork
{
    /// <summary>
    /// Description: IRepository
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:36:46
    /// </summary>
    public interface IRepository<T> where T : IAggregateRoot
    {
        IUnitOfWork UnitOfWork { get; }
    }
}
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Project.Domain.AggregatesModel
{

    /// <summary>
    /// Description: IProjectRepository
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:30:09
    /// </summary>
    public interface IProjectRepository : IRepository<Project>
    {
        Task<Project> GetAsync(int id);

        Project Add(Project project);

        void Update(Project project);
    }
}

  在此项目中,项目查看者和项目参与者都是需要现有项目后才能拥有,因此将项目作为聚合根来对待。

using Project.Domain.Events;
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Project.Domain.AggregatesModel
{
    /// <summary>
    /// Description: Project
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:30:23
    /// </summary>
    public class Project : Entity,IAggregateRoot
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        public int UserId { get; set; }

        /// <summary>
        /// 用户名称
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        ///  项目logo
        /// </summary>
        public string Avatar { get; set; }

        /// <summary>
        /// 公司名称
        /// </summary>
        public string Company { get; set; }

        /// <summary>
        /// 原BP文件地址
        /// </summary>
        public string OriginBPFile { get; set; }

        /// <summary>
        /// 转换后的BP文件地址
        /// </summary>
        public string FormatBPFile { get; set; }

        /// <summary>
        /// 是否显示敏感信息
        /// </summary>
        public bool  ShowSecurityInfo { get; set; }

        /// <summary>
        /// 公司所在省Id
        /// </summary>
        public int ProvinceId { get; set; }

        /// <summary>
        /// 公司所在省名称
        /// </summary>
        public string Province { get; set; }

        /// <summary>
        /// 公司所在城市Id
        /// </summary>
        public int CityId { get; set; }

        /// <summary>
        /// 公司所在城市名称
        /// </summary>
        public string City { get; set; }

        /// <summary>
        /// 区域Id
        /// </summary>
        public int AreaId { get; set; }

        /// <summary>
        /// 区域名称
        /// </summary>
        public string AreaName { get; set; }

        /// <summary>
        /// 公司成立时间
        /// </summary>
        public DateTime RegisterTime { get; set; }

        /// <summary>
        /// 项目基本信息
        /// </summary>
        public string Introduction { get; set; }

        /// <summary>
        /// 出让股份比例
        /// </summary>
        public string FinPercentage { get; set; }

        /// <summary>
        /// 融资阶段
        /// </summary>
        public string FinStage { get; set; }

        /// <summary>
        /// 融资金额 单位(万)
        /// </summary>
        public int FinMoney { get; set; }

        /// <summary>
        /// 收入 单位(万)
        /// </summary>
        public int Income { get; set; }

        /// <summary>
        /// 利润 单位(万)
        /// </summary>
        public int Revenue { get; set; }

        /// <summary>
        /// 估值 单位(万)
        /// </summary>
        public int Valuation { get; set; }

        /// <summary>
        /// 佣金分配方式 : 线下商议  等比例分配
        /// </summary>
        public int BrokerageOptions { get; set; }


        /// <summary>
        /// 是否委托给平台finbook
        /// </summary>
        public bool OnPlatform { get; set; }

        /// <summary>
        /// 可见范围设置
        /// </summary>
        public ProjectVisibleRule VisibleRule { get; set; }

        /// <summary>
        /// 根引用项目Id
        /// </summary>
        public int SourceId { get; set; }

        /// <summary>
        /// 上级引用项目Id
        /// </summary>
        public int ReferenceId { get; set; }

        /// <summary>
        /// 项目标签
        /// </summary>
        public string Tags { get; set; }

        /// <summary>
        /// 项目属性:行业领域、融资币种
        /// </summary>
        public List<ProjectProperty> Properties { get; set; }

        /// <summary>
        /// 贡献者列表
        /// </summary>
        public List<ProjectContributor> Contributors { get; set; }

        /// <summary>
        /// 查看者
        /// </summary>
        public List<ProjectViewer> Viewers { get; set; }


        /// <summary>
        /// 更新时间
        /// </summary>
        public DateTime UpdateTime { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreatedTime { get; set; }

        private Project Clone(Project source = null)
        {
            if (source == null)
            {
                source = this;
            }

            var newProject = new Project()
            {
                AreaId = source.AreaId,
                AreaName = source.AreaName,
                Avatar = source.Avatar,
                BrokerageOptions = source.BrokerageOptions,
                City = source.City,
                CityId = source.CityId,
                Company = source.Company,
                Contributors = new List<ProjectContributor> { },
                CreatedTime = DateTime.Now,
                FinMoney = source.FinMoney,
                FinPercentage = source.FinPercentage,
                FinStage = source.FinStage,
                FormatBPFile = source.FormatBPFile,
                Income = source.Income,
                Introduction = source.Introduction,
                OnPlatform = source.OnPlatform,
                OriginBPFile = source.OriginBPFile,
                Province = source.Province,
                ProvinceId = source.ProvinceId,
                ReferenceId = source.ReferenceId,
                RegisterTime = source.RegisterTime,
                Revenue = source.Revenue,
                ShowSecurityInfo = source.ShowSecurityInfo,
                SourceId = source.SourceId,
                Tags = source.Tags,
                UpdateTime = source.UpdateTime,
                UserId = source.UserId,
                Valuation = source.Valuation,
                Viewers = new List<ProjectViewer> { },
                VisibleRule = source.VisibleRule  == null ? null : new ProjectVisibleRule
                {
                    Visible = source.VisibleRule.Visible,
                    Tags = source.VisibleRule.Tags
                }
            };

            newProject.Properties = new List<ProjectProperty> { };
            foreach(var item in source.Properties)
            {
                newProject.Properties.Add(new ProjectProperty(item.Key, item.Text, item.Value));
            }

            return newProject;
        }

        /// <summary>
        /// 参与者得到项目拷贝
        /// </summary>
        /// <param name="contributorId"></param>
        /// <param name="source"></param>
        /// <returns></returns>
        public Project ContributorFork(int contributorId,Project source = null)
        {
            if(source == null)
            {
                source = this;
            }

            var newProject = Clone(source);

            newProject.UserId = contributorId;
            newProject.SourceId = source.SourceId;
            newProject.ReferenceId = source.ReferenceId;
            newProject.UpdateTime = DateTime.Now;

            return newProject;
        }


        public Project()
        {
            this.Viewers = new List<ProjectViewer>();
            this.Contributors = new List<ProjectContributor>();

            AddDomainEvent(new ProjectCreatedEvent { Project = this }); //添加创建项目事件
        }


        public void AddViewer(int userId,string userName,string avatar)
        {

            ProjectViewer viewer = new ProjectViewer() {
                UserId = UserId,
                UserName = userName,
                Avatar = avatar,
                CreatedTime = DateTime.Now
            };

            if(!Viewers.Any(v=>v.UserId == userId))
            {
                Viewers.Add(viewer);

                AddDomainEvent(new ProjectViewedEvent {
                    Company = this.Company,
                    Introduction = this.Introduction,
                    Viewer = viewer });
            }
            
        }

        public void AddContributor(ProjectContributor contributor)
        {
            if (!Contributors.Any(c => c.UserId == contributor.UserId))
            {
                Contributors.Add(contributor);
                AddDomainEvent(new ProjectJoinedEvent {
                    Company = this.Company,
                    Introduction = this.Introduction,
                    Contributor = contributor });
            }
        }
    }
}
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Text;

namespace Project.Domain.AggregatesModel
{

    /// <summary>
    /// Description: ProjectContributor
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:30:46
    /// </summary>
    public class ProjectContributor : Entity
    {
        public int ProjectId { get; set; }

        public int UserId { get; set; }

        public string UserName { get; set; }

        public string Avatar { get; set; }

        public DateTime CreatedTime { get; set; }

        /// <summary>
        /// 关闭者
        /// </summary>
        public bool IsCloser { get; set; }

        /// <summary>
        /// 参与者类型:1 财务顾问  2 投资机构
        /// </summary>
        public int ContributorType { get; set; }
    }
}
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Text;

namespace Project.Domain.AggregatesModel
{

    /// <summary>
    /// Description: ProjectProperty
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:31:01
    /// </summary>
    public class ProjectProperty : ValueObject
    {
        public ProjectProperty(string key,string text,string value)
        {
            this.Key = key;
            this.Text = text;
            this.Value = value;
        }

        public string Key { get; set; }

        public string Text { get; set; }

        public string Value { get; set; }

        public int ProjectId { get; set; } //违背了DDD值对象

        protected override IEnumerable<object> GetAtomicValues()
        {
            yield return Key;
            yield return Text;
            yield return Value;
        }
    }
}
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Text;

namespace Project.Domain.AggregatesModel
{

    /// <summary>
    /// Description: ProjectViewer
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:31:13
    /// </summary>
    public class ProjectViewer : Entity
    {
        public int ProjectId { get; set; }

        public int UserId { get; set; }

        public string UserName { get; set; }

        public string Avatar { get; set; }

        public DateTime CreatedTime { get; set; }
    }
}

  同时,当项目被创建、查看、参与的时候可能需要发布一些事件,所以我们定义如下领域事件。

using MediatR;
namespace Project.Domain.Events
{

    /// <summary>
    /// Description: ProjectCreatedEvent
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/10 22:15:56
    /// </summary>
    public class ProjectCreatedEvent :INotification
    {
        public AggregatesModel.Project Project { get; set; }
    }
}


using MediatR;
using Project.Domain.AggregatesModel;

namespace Project.Domain.Events
{

    /// <summary>
    /// Description: ProjectJoinedEvent
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/10 22:16:25
    /// </summary>
    public class ProjectJoinedEvent : INotification
    {
        public string Company { get; set; }

        public string Introduction { get; set; }

        public ProjectContributor Contributor { get; set; }
    }
}

using MediatR;
using Project.Domain.AggregatesModel;

namespace Project.Domain.Events
{

    /// <summary>
    /// Description: ProjectViewedEvent
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/10 22:16:09
    /// </summary>
    public class ProjectViewedEvent :INotification
    {
        public string Company { get; set; }

        public string Introduction { get; set; }

        public ProjectViewer Viewer { get; set; }
    }
}

 

  2、实现基础设施层

  在基础设施层中,我们将定义数据访问上下文和具体的仓储实现

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using Project.Domain.Seedwork;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Project.Infrastructure.EntityConfigurations;

namespace Project.Infrastructure
{

    /// <summary>
    /// Description: ProjectContext
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:26:13
    /// </summary>
    public class ProjectContext : DbContext, IUnitOfWork
    {
        private IMediator _mediator;

        public DbSet<Domain.AggregatesModel.Project> Projects { get; set; }

        public ProjectContext(DbContextOptions<ProjectContext> options,
            IMediator mediator) : base(options)
        {
            _mediator = mediator;
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new ProjectEntityConfiguration());
            modelBuilder.ApplyConfiguration(new ProjectPropertyConfiguration());
            modelBuilder.ApplyConfiguration(new ProjectPropertyConfiguration());
            modelBuilder.ApplyConfiguration(new ProjectViewerConfiguration());
            modelBuilder.ApplyConfiguration(new ProjectContributorConfiguration());

            //modelBuilder.Entity<Domain.AggregatesModel.Project>()
            //    .ToTable("Projects")
            //    .HasKey(p => p.Id);




        }

        public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken= default(CancellationToken))
        {
            //发送领域事件
            await _mediator.DispatchDomainEventsAsync(this);

            await base.SaveChangesAsync();

            return true;
        }
    }
}
using Project.Domain.AggregatesModel;
using Project.Domain.Seedwork;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using ProjectEntity = Project.Domain.AggregatesModel.Project;
using Microsoft.EntityFrameworkCore;

namespace Project.Infrastructure.Repositories
{

    /// <summary>
    /// Description: ProjectRepository
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 23:29:29
    /// </summary>
    public class ProjectRepository : IProjectRepository
    {
        private readonly ProjectContext _context;
        public IUnitOfWork UnitOfWork => _context;

        public ProjectRepository(ProjectContext cotnext)
        {
            _context = cotnext;
        }

        public ProjectEntity Add(ProjectEntity project)
        {
            if (project.IsTransient())
            {
                return _context.Add(project).Entity;
            }
            else
            {
                return project;
            }
        }

        public async Task<ProjectEntity> GetAsync(int id)
        {
            return await _context.Projects.Include(p => p.Properties)
                .Include(p => p.Viewers)
                .Include(p => p.Contributors)
                .Include(p => p.VisibleRule)
                .SingleOrDefaultAsync();
        }

        public void Update(ProjectEntity project)
        {
            _context.Update(project);
        }
    }
}
using MediatR;
using Project.Domain.Seedwork;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Infrastructure
{

    /// <summary>
    /// Description: MediatorExtension
    /// Author: Jesen
    /// Version: 1.0
    /// Date: 2019/8/8 22:25:51
    /// </summary>
    static class MediatorExtension
    {
        public static async Task DispatchDomainEventsAsync(this IMediator mediator, ProjectContext ctx)
        {
            var domainEntities = ctx.ChangeTracker
                .Entries<Entity>()
                .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());

            var domainEvents = domainEntities
                .SelectMany(x => x.Entity.DomainEvents)
                .ToList();

            domainEntities.ToList()
                .ForEach(entity => entity.Entity.ClearDomainEvents());

            var tasks = domainEvents
                .Select(async (domainEvent) => {
                    await mediator.Publish(domainEvent);
                });

            await Task.WhenAll(tasks);
        }
    }
}

 

  3、完成应用层

  在应用层中,我们采用CQRS架构,利用MediatR来实现Command和CommandHandler,在CommandHandler中调用ProjectContext的Save方法出发领域事件处理程序,其也在Application层实现,而在领域事件处理程序中,又可以触发集成事件,而在其他服务中订阅这个集成事件。

using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ProjectEntity = Project.Domain.AggregatesModel.Project;

namespace Project.Api.Applications.Commands
{
    public class CreateProjectCommand : IRequest<ProjectEntity>
    {
        public ProjectEntity Project { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Project.Domain;
using Project.Domain.AggregatesModel;
using ProjectEntity = Project.Domain.AggregatesModel.Project;

namespace Project.Api.Applications.Commands
{
    public class CreateProjectCommandHandler : IRequestHandler<CreateProjectCommand, ProjectEntity>
    {
        private IProjectRepository _projectRepository;

        public CreateProjectCommandHandler(IProjectRepository projectRepository)
        {
            _projectRepository = projectRepository;
        }

        public async Task<ProjectEntity> Handle(CreateProjectCommand request, CancellationToken cancellationToken)
        {
            _projectRepository.Add(request.Project);

            await _projectRepository.UnitOfWork.SaveEntitiesAsync();

            return request.Project;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DotNetCore.CAP;
using MediatR;
using Project.Api.Applications.IntergrationEvents;
using Project.Domain.Events;

namespace Project.Api.Applications.DomainEventHandlers
{
    public class ProjectCreatedDomainEventHandler : INotificationHandler<ProjectCreatedEvent>
    {
        private ICapPublisher _capPublisher;
        public ProjectCreatedDomainEventHandler(ICapPublisher capPublisher)
        {
            _capPublisher = capPublisher;
        }

        public Task Handle(ProjectCreatedEvent notification, CancellationToken cancellationToken)
        {
            var @event = new ProjectCreatedIntergrationEvent
            {
                CreatedTime = DateTime.Now,
                ProjectId = notification.Project.Id,
                UserId = notification.Project.UserId
            };
            _capPublisher.Publish("finbook.projectapi.projectcreated",@event);

            return Task.CompletedTask;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Api.Applications.IntergrationEvents
{
    public class ProjectCreatedIntergrationEvent
    {
        public int ProjectId { get; set; }

        public int UserId { get; set; }

        public DateTime CreatedTime { get; set; }

        public int FromUserId { get; set; }

        public string FromUserName { get; set; }

        public string FromUserAvatar { get; set; }

        /// <summary>
        /// 项目logo
        /// </summary>
        public string ProjectAvatar { get; set; }

        /// <summary>
        /// 公司名称
        /// </summary>
        public string Company { get; set; }

        /// <summary>
        /// 项目介绍
        /// </summary>
        public string Introduction { get; set; }

        /// <summary>
        /// 项目标签
        /// </summary>
        public string Tags { get; set; }

        /// <summary>
        /// 融资阶段
        /// </summary>
        public string FinStage { get; set; }

    }
}

  而Query就显得比较简单

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Project.Api.Applications.Queries
{
    public interface IProjectQueries
    {
        Task<dynamic> GetProjectByUserId(int userId);

        Task<dynamic> GetProjectDetail(int projectId);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;
using Project.Infrastructure;

namespace Project.Api.Applications.Queries
{
    public class ProjectQueries : IProjectQueries
    {
        private readonly ProjectContext _context;

        public ProjectQueries(ProjectContext context)
        {
            _context = context;
        }

        public async Task<dynamic> GetProjectByUserId(int userId)
        {
            using (var conn = _context.Database.GetDbConnection())
            {
                conn.Open();

                var sql = @"SELECT Projects.Id,Projects.Avatar,Projects.Company,
Projects.FinStage,Projects.Introduction,Projects.ShowSecurityInfo,Projects.CreatedTime
FROM Projects WHERE Projects.UserId=@userId;";

                var result = await conn.QueryAsync<dynamic>(sql,new { userId });

                return result;
            }
        }

        public async Task<dynamic> GetProjectDetail(int projectId)
        {
            //using(var conn = new MySqlConnection(_connStr))
            using (var conn = _context.Database.GetDbConnection())
            {
                conn.Open();

                string sql = @"SELECT
                                b.Visible,
                                b.Tags,
                                a.UserId,
                                a.Avatar,
                                a.Company,
                                a.OriginBPFile,
                                a.FormatBPFile,
                                a.ShowSecurityInfo,
                                a.Province,
                                a.AreaName,
                                a.RegisterTime,
                                a.Introduction,
                                a.FinPercentage,
                                a.FinStage,
                                a.FinMoney,
                                a.Income,
                                a.Revenue,
                                a.Valuation,
                                a.BrokerageOptions,
                                a.OnPlatform,
                                a.Tags,
                                a.UserName,
                                a.City 
                            FROM
                                Projects AS a
                                INNER JOIN ProjectVisibleRule AS b ON a.Id = b.ProjectId 
                            WHERE
                                a.Id = @projectId;";

                var result = await conn.QueryAsync<dynamic>(sql, new { projectId });

                return result;
            }
        }
    }
}

  最后需要注入MediatR

services.AddScoped<IProjectQueries, ProjectQueries>();
services.AddScoped<IRecommendService, RecommendService>();
services.AddScoped<IProjectRepository, ProjectRepository>();

//从CreateProjectCommand所在程序集中扫描相应的类进行注入
services.AddMediatR(typeof(Applications.Commands.CreateProjectCommand).Assembly);
//services.AddMediatR();

 

posted @ 2020-12-07 23:52  柠檬笔记  阅读(405)  评论(0编辑  收藏  举报