ENode 1.0 – 整体架构介绍

前言

今天凡只开心之光景,又是星期天,可以欣慰轻松的刻画写篇了。经过了大约3年的DDD理论积累,以及去年开春的首先只本子的event
sourcing框架的支付和项目实践经验,再经过今年上半年应用业余时间的宏图及开发,我的enode框架终于得跟豪门见面了。

打Eric
Evan提出DDD领域让设计吧曾过了多年了,现在都出为数不少口于习或者履DDD。但是我发现脚下会支持DDD开发之框架还非多,至少在国内还无多。据我所知道之java和.net平台,国外比较显赫的发出:基于java平台的是axon
framework,该框架很欢,作者也酷勤奋,该框架已经当有事实上商业型被采取了,算比较成功;基于.net平台的是ncqrs,该框架早于于外向,但现行未曾进步了,因为几乎没人于保护,让人老失望;国内产生:banq的jdon
framework可支持DDD+CQRS+EventSourcing的开支,但是她是基于java平台的,所以对于.net平台的食指,没什么实际用;.net平台,开源之首要就是是田园里的晴阳兄开发之apworks框架。晴阳兄在DDD方面,在国内的献特别充分,写了森DDD系列之篇章,框架和案例并行,很不错。当然,我所关心的严谨是c#跟java语言的框架,基于scala等其他语言实现之框架为时有发生众多,这里虽不一一例举了。

地方这么多框架都发独家的特色和优势,这里就是非多开评价了,大家产生趣味的好失去探视吧。我最主要想介绍的凡本身的enode框架,框架的特点,以及利用的前提条件。

框架简介

  1. 框架名称:enode
  2. 框架特色:提供一个基于DDD设计思想,实现了CQRS + EDA + Event
    Sourcing + In
    Memory这些架构模式的,支持负载均衡的,轻量级应用开发框架。
  3. 开源地址:https://github.com/tangxuehua/enode
  4. 圆使用enode的一个论坛的地方:https://github.com/tangxuehua/forum
  5. nuget包Id:enode

使该框架需要了解还是遵守以下几单约定:

  1. 一个command只允许造成一个聚合根的改动或一个聚合根的始建,如果违反此规则,则框架不容许;
  2. 比方一个用户操作会涉及多只聚合根的改动,则要经saga (process
    manager)来促成;拥抱最终一致性,简单的游说不怕是经过以command+domain
    event不决的串联来最终落实最终一致性;如果想彻底底知情enode哪里与众不同,可以拘留一下源代码中的BankTransferExample,相信这会让你知道什么是自所说之事件驱动设计;
  3. 框架的主干编程思想是异步消息处理加最终一致性,所以,如果你想实现高一致性需求,那是框架不太符合,至少目前莫供这么的支撑;
  4. 框架的计划目标不是指向企业应用开发,传统企业应用一般访问量不生且要求高一致性事务;enode框架更多的凡对互联网使用,特别是也一些需支持访问量十分、高性能、可伸缩且允许最终一致性的互联网站点提供支撑;看罢:可伸缩性最佳实践:来自eBay的经历的食指应了解要兑现一个而伸缩的互联网应用,异步编程和末段一致性是得的;另外,因为如果数据量一杀,那咱们一般会把数据分开存放,这就象征,如果您还眷恋实现高一致性,那便使赖分布式事务。但是雅倒霉,分布式事务的资本代价不过强。伸缩、性能与响应延迟还遭遇分布式事务协调资金的反面影响,随着靠的资源数量和用户访问数之上升,这些指标还见面因为几哪里级数恶化。可用性亦负限制,因为兼具因的资源且得就各类。
  5. 框架定位:目前稳被才台机械及运行的么应用内的CQRS架构前提下之command端的落实;如果一旦兑现多台机械多只利用内的分布式集成,则大家需要再进一步借助ESB来与重高层的SOA架构集成;

框架架构图:

图片 1

CQRS架构图

方的架构图是enode框架的其中贯彻架构。当然,上面这个架构图并无是总体的CQRS架构图,而是CQRS架构图中command端的贯彻架构。完整的CQRS架构图一般如下:

图片 2

打达到图我们得以视,传统的CQRS架构图,一般画的且比大范围,command端具体如何促成,实现方案有死多种。而enode框架,只是内同样栽实现。

框架的严重性中贯彻认证

  1. 率先,client会发送command给command service,command
    service接受到command后,会透过一个command queue
    router来路由该command应该坐哪个command queue,每个command
    queue就是一个音讯队列,队列里存放command。该消息队列是当地队列,但是支持信息的持久化,也就是说command被放入队列后,就算机器挂了,下次机械还开后,消息也未会见掉。另外,command
    queue我们得依据需要配备多只,上图为表示,只写了有限独;
  2. command queue的出口端,有一个command processor,command
    processor的任务是拍卖command。但是command
    processor本身不直处理command,直接处理command的凡command
    processor内部的组成部分worker线程,每个worker线程会不断的从command
    queue中取出command,然后按图中标明出之5单步骤对command进行拍卖。可以视,由于command
    processor中之worker线程都是当互动工作之,所以我们好窥见,同一时刻,会有多单command在受以处理。为什么要如此做?因为client发送command到command
    queue的快迅猛,比如每秒发送1W单command过来,也不怕是出新是1W,但是command
    processor如果中间只有单线程在拍卖command,那速度跟不上这个并发量,所以我们需要规划支撑多只worker同时处理command,这样延迟就会见减低;我们打架构图可以看出,command
    processor获取聚合根是由内存缓存(如支持分布式缓存的redis)获取,性能于高;持久化事件,用底凡MongoDB,由于mongoDB性能也坏高;如果当事件持久化到单台MongoDB
    server还是产生瓶颈问题,那咱们得以本着MongoDB
    server举行集群,然后针对事件开展sharding,将不同之event存储到不同的MongoBD
    Server,这样,事件之持久化也非会见成为瓶颈;这样,整个command
    processor的拍卖性能理论及得死高,当然我还尚无测试了集群情况下性能好上多少;单个mongodb
    server,持久化事件的特性,5K不成问题;这里有一些放贷这于征下,被持久化的莫过于不是单个事件,而是一个轩然大波流,即EventStream。为什么是事件流是因为单个聚合根一坏或有持续一个世界事件,但是这些事件仍同吃持久化,所以规划思路是拿这些事件设计呢一个事件流,然后以这波流作为同样久mongodb的记录插入到mongodb;事件流在mongodb中之主键是聚合根ID+事件流的版本号,通过这半只旅字段作为主键,用来兑现乐观锁;假如发生半点个事件流都是对准同一个聚合根的,且他们的版本号相同,那插入到mongodb时,会报主键索引冲突,这虽是出新冲突了。需要针对command进行自动重试(enode框架会拉扯您活动开少这个自动重试)来缓解是题材;
  3. command
    processor中的worker处理了一个command后,会拿来的事件发布让一个当的event
    queue。同样,内部也会见有一个event queue
    router来路由到底该坐哪个event queue。那么event
    queue中之事件接下去要被如何处理为?也即是event
    processor会做身事情也?很简短,就是分发事件被持有的轩然大波订阅者,即dispatch
    event to subscribers。那这些event
    subscribers都见面召开啊业务啊?一般是开简单栽处理:1)因为是下CQRS架构,所以我们不克独持久化领域事件,还要经世界事件来更新CQRS的查询端数据库(这种为创新查询库的轩然大波订阅者老外一般叫denormalizer);由于更新查询库没有必要同步,所以计划event
    queue;2)上面提到过,有些操作会影响多个聚合根,比如银行转账,订单处理,等。这些操作本质上是一个流程,所以我们的方案是通过在世界事件之event
    handler中发送command来异步的实现串联整个处理流程;当然,如何落实者流程,还是来无数题目亟需讨论。我个人觉得比靠谱的方案是经process
    manager,类似BPM的沉思,国外也产生很多丁管它们称作saga。对saga或process
    manager感兴趣的看官,可以看微软的这例子:http://msdn.microsoft.com/en-us/library/jj591569.aspx,对于如何用enode来实现一个process
    manager,由于信息极多,所以自己连下会刻画一篇稿子特别系统的介绍。

想起框架所祭的关键技术

因整个enode框架的架构图以及地方的文字描述说明,我们以圈一下者最初步框架简介中干的框架所用的关键技术。

  1. DDD:指架构图中之domain
    model,采用DDD的思量去分析规划实现,enode框架会供实现DDD所必不可少的基类聚合根以及触发领域事件的支撑;
  2. CQRS:指任何enode架构实现之是CQRS架构中的command端,CQRS架构的查询端,enode框架没做任何限制,我们好擅自设计;
  3. EDA:指合编程模型的思路,都使根据事件驱动的想想,也不怕是圈子模型的状态更改是根据响应事件的,聚合根之间的互,也未是依据事务,而是因事件驱动和响应;
  4. Event
    Sourcing:中文意思是事件起源,关于什么是事件源自,可以拘留一下这篇文章。通过波源自,我们可不用ORM来持久化聚合根,而是如持久化领域事件即可,当我们只要还原聚合根时只要针对该聚合根进行相同不善事件起源即可;
  5. In Memory:是依靠合domain
    model的拥有数据都存储在内存缓存中,比如分布式缓存redis中,且缓存永远不见面给放出。这样当我们设获取聚合根时,只要打外存缓存拿即可,所以受in
    memory;
  6. NoSQL:是指enode用到了redis,mongodb这样的nosql产品;
  7. 负载均衡支持:是依赖,基于enode框架的应用程序,可以好的支持负载均衡;因为应用程序本身是管状态的,in
    memory是储存在大局的redis分布式缓存中,独立为以本身;而event
    store则是为此MongoDB,同样也是全局的,且为支撑集群。所以,我们可以用基于enode框架开发的应用程序部署任意多客在不同之机,然后开负载均衡,从而让我们的应用程序支撑更胜的产出访问。

框架API使用简介

框架初始化

public void Initialize()
{
    var connectionString = "mongodb://localhost/EventDB";
    var eventCollection = "Event";
    var eventPublishInfoCollection = "EventPublishInfo";
    var eventHandleInfoCollection = "EventHandleInfo";

    var assemblies = new Assembly[] { Assembly.GetExecutingAssembly() };

    Configuration
        .Create()
        .UseTinyObjectContainer()
        .UseLog4Net("log4net.config")
        .UseDefaultCommandHandlerProvider(assemblies)
        .UseDefaultAggregateRootTypeProvider(assemblies)
        .UseDefaultAggregateRootInternalHandlerProvider(assemblies)
        .UseDefaultEventHandlerProvider(assemblies)

        //使用MongoDB来支持持久化
        .UseDefaultEventCollectionNameProvider(eventCollection)
        .UseDefaultQueueCollectionNameProvider()
        .UseMongoMessageStore(connectionString)
        .UseMongoEventStore(connectionString)
        .UseMongoEventPublishInfoStore(connectionString, eventPublishInfoCollection)
        .UseMongoEventHandleInfoStore(connectionString, eventHandleInfoCollection)

        .UseAllDefaultProcessors(
            new string[] { "CommandQueue" },
            "RetryCommandQueue",
            new string[] { "EventQueue" })
        .Start();
}

Command定义

[Serializable]
public class ChangeNoteTitle : Command
{
    public Guid NoteId { get; set; }
    public string Title { get; set; }
}

发送Command到ICommandService

var commandService = ObjectContainer.Resolve<ICommandService>();
commandService.Send(new ChangeNoteTitle { NoteId = noteId, Title = "Modified Note" });

Command Handler

public class ChangeNoteTitleCommandHandler : ICommandHandler<ChangeNoteTitle>
{
    public void Handle(ICommandContext context, ChangeNoteTitle command)
    {
        context.Get<Note>(command.NoteId).ChangeTitle(command.Title);
    }
}

Domain Model

[Serializable]
public class Note : AggregateRoot<Guid>,
    IEventHandler<NoteCreated>,
    IEventHandler<NoteTitleChanged>
{
    public string Title { get; private set; }
    public DateTime CreatedTime { get; private set; }
    public DateTime UpdatedTime { get; private set; }

    public Note() : base() { }
    public Note(Guid id, string title) : base(id)
    {
        var currentTime = DateTime.Now;
        RaiseEvent(new NoteCreated(Id, title, currentTime, currentTime));
    }

    public void ChangeTitle(string title)
    {
        RaiseEvent(new NoteTitleChanged(Id, title, DateTime.Now));
    }

    void IEventHandler<NoteCreated>.Handle(NoteCreated evnt)
    {
        Title = evnt.Title;
        CreatedTime = evnt.CreatedTime;
        UpdatedTime = evnt.UpdatedTime;
    }
    void IEventHandler<NoteTitleChanged>.Handle(NoteTitleChanged evnt)
    {
        Title = evnt.Title;
        UpdatedTime = evnt.UpdatedTime;
    }
}

Domain Event

[Serializable]
public class NoteTitleChanged : Event
{
    public Guid NoteId { get; private set; }
    public string Title { get; private set; }
    public DateTime UpdatedTime { get; private set; }

    public NoteTitleChanged(Guid noteId, string title, DateTime updatedTime)
    {
        NoteId = noteId;
        Title = title;
        UpdatedTime = updatedTime;
    }
}

Event Handler

public class NoteEventHandler :
    IEventHandler<NoteCreated>,
    IEventHandler<NoteTitleChanged>
{
    public void Handle(NoteCreated evnt)
    {
        Console.WriteLine(string.Format("Note created, title:{0}", evnt.Title));
    }
    public void Handle(NoteTitleChanged evnt)
    {
        Console.WriteLine(string.Format("Note title changed, title:{0}", evnt.Title));
    }
}

后续要讨论的关键问题

  1. 既是信息令,那怎么管信息未会见掉;
  2. 什么保证信息至少为实践同一不好,且无能够吃重新执行;
  3. 争保管信息并未尽成功就是非能够丢弃,也就是是求信息队列支持工作;
  4. 因为是多线程并行持久化事件而是大半尊机器集群负载均衡配置之,那如何管领域事件于持久化的逐一与发布暨事件订阅者的一一完全一致;
  5. 全套架构中,基于redis实现的memory
    cache以及因mongodb实现之eventstore,是少数只重点的银行,如何确保高吞吐量和可用性;
  6. 因为事件是相互持久化的,那要是撞并发冲突如何化解?
  7. 令的重试如何促成?消息队列中的信的重试机制如何兑现?
  8. 既抛弃了高一致性的作业概念,而用process
    manager来实现聚合根交互,那什么样切实落实一个process manager?

眼前小想到以上8只自我以为比重要的问题,我会以属下的稿子被,一一讨论这些题目的缓解思路。我看写这种介绍框架的篇章,一方面要介绍框架本身,更着重之是只要告知别人而计划和落实框架时遇的题材及解决思路。要管这分析以及缓解之笔触写出来,这才是针对读者意义极其深之;

网站地图xml地图