FFLIb Demo CQRS

使用FFLIB 构建了一个demo,该demo模拟了一个常见的游戏后台架构,该demo主要有一下亮点:

  • FFLIB 实现进程间通信非常方便
  • 基于CQRS 思想构建LogicServer
  • 使用Event Publish/Subscribe 实现各个模块的解耦合
  • 基于Event 实现实体对象的单元测试,在你gtest中,利用eventmock,同时利用event  做验证,单元测试就是一个Givenevent,先提供条件), WhenCommand,触发操作), ExpectEvent,期望结果是否发生)。

模拟后台进程的通信

由于本demo 只在于演示fflibdemo中的细节没有做过多处理,主要通讯流程就是client – gatewayBroker – LogicServer

 

GatewayBroker 转发消息

Gatewaybroker 扮演的角色为接受连接,转发消息。示例代码如下:

int gateway_service_t::handle_common_logic(gate_msg_tool_t& msg_, socket_ptr_t sock_)
{struct lambda_t{static void callback(common_msg_t::out_t& msg_, long uid_){//! send to client, add to gateway user map//! msg_sender_t::send_to_client(sock_, msg_);
        }};long uid = sock_->get_data<client_session_t>()->uid;common_msg_t::in_t dest_msg;dest_msg.uid = uid;dest_msg.content = msg_.packet_body;singleton_t<msg_bus_t>::instance().get_service_group("logic")->get_service(0)->async_call(dest_msg, binder_t::callback(&lambda_t::callback, uid));return 0;
}

LogicServer 各个逻辑模块处理请求

LogicServer 接收到消息后,将消息交由特定的逻辑模块处理,所有的逻辑模块接口都专门处理一种cmd,并且这些接口都已经注册到BUS中了。故LogicServer 将消息publishBUS中即可:

int logic_service_t::common_msg(common_msg_t::in_t& msg_, rpc_callcack_t<common_msg_t::out_t>& cb_)
{common_msg_t::out_t ret;cb_(ret);uint32_t* len = (uint32_t*)(msg_.content.c_str());string name(msg_.content.c_str()+4, *len);BUS.publish(name, msg_.content);return 0;
}

BUS 的细节

Service 中定义的接口,需要注册到BUS中,订阅相关的CMD,示例代码:

int task_service_t::start()
{subscriber_t subscriber;subscriber.reg<accept_task_cmd_t>(this).reg<complete_task_cmd_t>(this);BUS.subscribe(subscriber);return 0;
}void task_service_t::handle(const accept_task_cmd_t& cmd_)
{USER_MGR.get_user(cmd_.uid).get_tasks().accet_task(cmd_.tid);
}void task_service_t::handle(const complete_task_cmd_t& cmd_)    
{USER_MGR.get_user(cmd_.uid).get_tasks().complete_task(cmd_.tid);
}

将特定的消息投递给特定接口只是BUS的功能之一,它也负责发布event eventcmd的区别是cmd是用户的操作,它会触发特定的实体逻辑,逻辑检查ok,将会创建某个或某些event,这些event会触发某些实体对象的数据改变。所有cmdevent都继承于type_i

class type_i
{
public:virtual ~ type_i(){}virtual int get_type_id() const { return -1; }virtual const string& get_type_name() const {static string foo; return foo; }virtual void   decode(const string& data_) {}virtual string encode()                    { return "";} 
};

 

其中typeidtypename都不需要使用者自己定义,有一个类event_t  会自动为其生成。示例代码如下:

class task_accepted_t: public event_t<task_accepted_t>
{
public:task_accepted_t(int task_id_ =0, int dest_value_ = 0):task_id(task_id_),dest_value(dest_value_){}int task_id;int dest_value;
};

BUS event被发布时,所有的订阅者都会被调用:

virtual int publish(const event_i& event_){return call(event_.get_type_id(), event_);}virtual int publish(const command_i& cmd_){return call(cmd_.get_type_id(), cmd_);}
int call(int type_id_, const type_i& obj_){int num = 0;pair<subscriber_t::callback_multimap_t::iterator, subscriber_t::callback_multimap_t::iterator> ret;ret = m_callbacks.equal_range(type_id_);for (subscriber_t::callback_multimap_t::iterator it = ret.first; it != ret.second; ++it){try{++num;it->second->callback(&obj_);}catch(exception& e){cout <<"bus exception:" << e.what() <<"\n";continue;}return 0;}return num;} 

Logicserver 的细节

LogicServer的设计

LogicServer 是后台程序中最复杂的部分,应尽量保证其可扩展性。在本demo中,遵循如下原则:

  • 实体对象封装所有的业务逻辑,如Usertasks 封装用户所有的任务相关操作
  • 实体对象内部分成两部分,一部分为借口,如accept,用于验证用户操作是否有效,若无效抛出异常,若有效,创建evnet。另一部分专门处理event,当有event触发,修改对象内部数据,同时event也会被publishBUS 中,这样其他逻辑模块也可以进行其他处理。示例代码:
void user_tasks_t::accet_task(int task_id_)
{if (m_tasks.find(task_id_) != m_tasks.end()) throw task_exception_t("tid exist");apply_change(task_accepted_t(task_id_, 100));
}
void user_tasks_t::apply(const task_accepted_t& event_)
{task_ino_t task_info(event_.task_id, event_.dest_value, TASK_ACCEPTED);m_tasks.insert(make_pair(event_.task_id, task_info));
}
void apply_change(const T& event_, bool new_change_ = true){apply(event_);if (new_change_){BUS.publish(event_);}
}
  • Service 负责处理cmd,根据不同的cmd,调用实体对象的接口 
  • 使用Event做单元测试

单元测试流程

Given:

在测试实体对象特定的接口时,需要mock操作,由于实体对象的所有修改都是由Event 触发的,mock操作只是按照顺序提供给实体对象event即可:

//! 先 mock出数据 只需给对象提供相应的event即可
    task_accepted_t e;e.task_id = 100;e.dest_value = 200;
user_task.apply_change(e, false); 

When 

Event given完毕后,触发实体的接口,并且测试接口是否按照预定的逻辑操作,如验证失败是否抛出异常。

//! test interface

    EXPECT_THROW(user_task.accet_task(100), user_tasks_t::task_exception_t);

Expect

当调用实体对象时,若逻辑争取,会触发一些event产生,由于实体对象的数据不能被直接验证是否修改争取,但是可以通过验证event是否按照预想的顺序触发来达到目的。

class task_event_counter_t
{
public:task_event_counter_t():task_accepted_counter(0){}void  handle(const task_accepted_t& e_){task_accepted_counter ++;}int task_accepted_counter;
};#define EVENT_COUNTER (singleton_t<task_event_counter_t>::instance())
//! task_accepted_t will be triggeruser_task.accet_task(200);
EXPECT_TRUE(EVENT_COUNTER.task_accepted_counter == 1);

如上代码所示, .accet_task()成功会触发, task_accepted_t 事件,通过验证此事件是否被触发,即可验证实体对象是否操作正常。

备注

示例代码地址:http://ffown.googlecode.com/svn/trunk/example/game_framework/

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294699.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【自定义标签开发】01-标签简介和开发第一个标签

自定义标签简介自定义标签主要用于移除Jsp页面中的java代码。要使用自定义标签移除jsp页面中的java代码&#xff0c;只需要完成以下两个步骤:1.编写一个实现Tag接口的java类&#xff0c;把页面java代码移到这个java类中(标签处理器类)。2.编写标签库描述(tld)文件&#xff0c;在…

三联《少年》创刊,各领域佼佼者畅言新知,帮少年建立思维素养体系!

▲点击查看很多中国小孩的成长是断层的。10岁前被视作可爱稚子&#xff0c;被大人护着走&#xff1b;18岁猛然被定义为成年人&#xff0c;要选择大学、专业&#xff0c;开始面对感情。中间的人生呢&#xff1f;“你是个学生&#xff0c;学习是本职&#xff0c;现在谈什么人生&a…

趁爸妈不在家约男朋友回家吃饭,然而......

1 有没有男主很脆弱的电影&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 成长期-成熟期-完全体-究极体▼3 千万别在家乱放星巴克的袋子&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 发生了很奇怪的事情▼5 只有你想不到&#xff0c;没…

java泛型不是计算运行时的数据类型

2019独角兽企业重金招聘Python工程师标准>>> package com.ada.data.entity;import java.io.Serializable;import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuper…

C#内建接口:IComparable

这节开一个新的系列&#xff1a;C#内建接口。主要给大家讲一下C#内部给我们定义的一些常用的接口&#xff0c;以及它们是怎么使用的。基本上一节讲一个吧&#xff0c;本节先从IComparable开始。01了解IComparable一般的&#xff0c;值类型的数据比较大小&#xff0c;就是看它俩…

script的defer和async

我们常用的script标签&#xff0c;有两个和性能、js文件下载执行相关的属性&#xff1a;defer和async defer的含义【摘自https://developer.mozilla.org/En/HTML/Element/Script】 This Boolean attribute is set to indicate to a browser that the script is meant to be exe…

WWDC 2013 Session笔记 - Xcode5和ObjC新特性

这是我的WWDC2013系列笔记中的一篇&#xff0c;完整的笔记列表请参看这篇总览。本文仅作为个人记录使用&#xff0c;也欢迎在许可协议范围内转载或使用&#xff0c;但是还烦请保留原文链接&#xff0c;谢谢您的理解合作。如果您觉得本站对您能有帮助&#xff0c;您可以使用RSS或…

难以摸透的直男脑回路......

1 你年纪不小了&#xff0c;该结婚了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 泰国小哥低成本Cos赛博朋克2077 ▼3 郑爷爷也太会了吧▼4 奇奇怪怪的知识又增加了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 酸奶今天7.1折&#xff08;via.…

ansible备份mysql_ansible做mysql备份和安全加固

2019独角兽企业重金招聘Python工程师标准>>>1. mysql备份A patch adding nameall was added to the mysql_db module on May 12, 2015, so the recommended way to dump all databases is:# Dumps all databases to hostname.sql- mysql_db: statedump nameall target…

ICMP

ICMP是网络层协议 能反馈和解决网络层的问题 ping traceroute 转载于:https://www.cnblogs.com/YDDMAX/p/5361165.html

今晚包饺子吗?会露馅的那种......

1 让水龙头滴水流动&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 来自干饭人的答题卷▼3 一心只想搞钱&#xff08;via.银教授&#xff09;▼4 奇奇怪怪的东西又增加了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 今晚过来吗&#xff1f;▼6 好…

公司僵尸帐号引发了一系列的入侵事件-细说密码强度验证的重要性

一、前言 每个公司几乎都会有一个公司的内部系统&#xff0c;每个员工的入职的的时候都会给开一个帐号&#xff0c;一般开帐号的这个人不会考虑帐号的安全性&#xff0c;用户名大多都是员工的姓名或者工号&#xff0c;密码也是姓名或者工号或者123456&#xff0c;如果可以输入1…

idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

课程简介&#xff1a;课程目标&#xff1a;通过本课程学习&#xff0c;深入理解Java线程池&#xff0c;提升自身技术能力与价值。适用人群&#xff1a;具有Java多线程基础的人群&#xff0c;希望深入理解线程池底层原理的人群。课程概述&#xff1a;多线程的异步执行方式&#…

C# WPF MVVM开发框架Caliburn.Micro快速搭建③

01—启动项目打开Visual Studio创建一个名为“Caliburn.Micro.Hello”的新WPF应用程序添加对Caliburn.Micro Nuget包的引用,最新的版本是4.0.173&#xff0c;更新日期2021年5月9日 (2021/5/9)删除“MainWindow.xaml”并从“App.xaml”中删除StartupUri&#xff0c;使其如下所示…

中国院士最多的县:共走出26位院士,百位高校校长,一万名教授

全世界只有3.14 % 的人关注了爆炸吧知识本文由科研大匠&#xff08;Id:keyandajiang&#xff09;综合整理自学术志、网易新闻、算法与数学之美“昔孟母&#xff0c;择邻处&#xff0c;子不学&#xff0c;断机杼”&#xff0c;从孟母为了给儿子创造良好的读书环境&#xff0c;以…

linux下A免密码登录B

linux下A免密码登录B现在有两台linux服务器A&#xff1a;192.168.1.111&#xff0c;B&#xff1a;192.168.1.1121、在A&#xff08;192.168.123.111&#xff09;生成公钥私钥对&#xff0c;命令如下&#xff1a;ssh-keygen -t rsa -P ‘’,如图&#xff1a;-P表示密码&#xf…

mysql between 查询不出来_mysql的语句优化

(1)mysql避免全表扫描1、应尽量避免在 where 子句中对字段进行 null 值判断&#xff0c;否则将导致引擎放弃使用索引而进行全表扫描&#xff0c;如&#xff1a; select id from t where num is null,不能用null作索引&#xff0c;任何包含null值的列都将不会被包含在索引中。即…

大开眼界!终于等到这部每一帧都是壁纸的纪录片!

全世界只有3.14 % 的人关注了爆炸吧知识中国&#xff0c;拥有七大水系&#xff0c;超过2600个自然湖泊&#xff0c;299.7万平方公里海洋面积。生活在陆地&#xff0c;周遭的一切都是被水体所包裹。但还从来没有哪一部纪录片&#xff0c;系统探秘过中国水下。水面之上&#xff0…

Binary Search二分法搜索C++程序

二分法基本上学计算机的都听过&#xff0c;但是有人不知道的就是其实二分法是减治法的思想。 所谓减治法和分治法有一个主要差别就是减治法是减去一般&#xff0c;就是分治之后只需要解决原问题的一半就可以了得到全局问题的解了。所以速度很快。 下面是二分法的递归程序和非递…

.NET 6新特性试用 | Nuget包验证

前言我们常常需要将.NET类库打包成Nuget包&#xff0c;以便多个项目公用。一旦修改类库&#xff0c;尽管代码可以运行&#xff0c;并成功打包成新版本&#xff0c;看起来一切正常&#xff0c;但是你无法保证该更改是安全且兼容的。而在.Net 6中&#xff0c;提供了包验证工具&am…