日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

FFLIb Demo CQRS

發布時間:2023/12/9 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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/

?

總結

以上是生活随笔為你收集整理的FFLIb Demo CQRS的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。