使用Golang实现一套流程可配置,适用于广告、推荐系统的业务性框架——简单应用
在诸如广告、推荐等系统中,我们往往会涉及过滤、召回和排序等过程。随着系统业务变得复杂,代码的耦合和交错会让项目跌入难以维护的深渊。于是模块化设计是复杂系统的必备基础。这篇文章介绍的业务框架脱胎于线上多人协作开发、高并发的竞价广告系统,在实践中不停被优化,直到易于理解和应用。
基础组件
Handler
在系统中,我们定义一个独立的业务逻辑为一个Handler。
比如过滤“机型”信息的逻辑可以叫做DeviceFilterHandler,排序的逻辑叫SortHandler。
Handler的实现也很简单,只要实现frame.HandlerBaseInterface接口和它对应的方法即可(见github):
package mainimport ("fmt""ghgroups/frame"ghgroupscontext "ghgroups/frame/ghgroups_context""reflect"
)type ExampleHandler struct {frame.HandlerBaseInterface
}func NewExampleHandler() *ExampleHandler {return &ExampleHandler{}
}// ///
// ConcreteInterface
func (e *ExampleHandler) Name() string {return reflect.TypeOf(*e).Name()
}// ///
// HandlerBaseInterface
func (e *ExampleHandler) Handle(*ghgroupscontext.GhGroupsContext) bool {fmt.Printf("run %s", e.Name())return true
}
ConcreteInterface
在系统中,我们要求每个组件都要有名字。这样我们可以在配置文件中指定它在流程中的具体位置。
组件通过继承接口ConcreteInterface,并实现其Name方法来暴露自己的名称。它可以被写死(如上例),也可以通过配置文件来指定(见后续案例)。
type ConcreteInterface interface {Name() string
}
HandlerBaseInterface
处理业务逻辑的代码需要在Handle(context *ghgroupscontext.GhGroupsContext) bool中实现的。上例中,我们只让其输出一行文本。
这个方法来源于HandlerBaseInterface接口。
type HandlerBaseInterface interface {ConcreteInterfaceHandle(context *ghgroupscontext.GhGroupsContext) bool
}
因为HandlerBaseInterface 继承自ConcreteInterface ,所以我们只要让自己构建的Handler继承自HandlerBaseInterface,并实现相应方法即可。
应用
一般一个复杂的业务不能只有一个Handler,但是为了便于方便讲解,我们看下怎么运行只有一个Handler的框架(见github)。
package mainimport ("fmt""ghgroups/frame""ghgroups/frame/constructor"ghgroupscontext "ghgroups/frame/ghgroups_context""ghgroups/frame/utils""reflect"
)func main() {constructor := utils.BuildConstructor("")constructor.Register(reflect.TypeOf(ExampleHandler{}))mainProcess := reflect.TypeOf(ExampleHandler{}).Name()run(constructor, mainProcess)
}func run(constructor *constructor.Constructor, mainProcess string) {if err := constructor.CreateConcrete(mainProcess); err != nil {fmt.Printf("%v", err)}if someInterfaced, err := constructor.GetConcrete(mainProcess); err != nil {fmt.Printf("%v", err)} else {if mainHandlerGroup, ok := someInterfaced.(frame.HandlerBaseInterface); !ok {fmt.Printf("mainHandlerGroup %s is not frame.HandlerBaseInterface", mainProcess)} else {context := ghgroupscontext.NewGhGroupsContext(nil)// context.ShowDuration = truemainHandlerGroup.Handle(context)}}
}
在main函数中,我们需要向对象构建器constructor注册我们写的Handler。然后调用run方法,传入构建器和需要启动的组件名(mainProcess)即可。运行结果如下
ExampleHandler
run ExampleHandler
第一行是框架打印的流程图(目前只有一个),第二行是运行时ExampleHandler的Handle方法的执行结果。
HandlerGroup
HandlerGroup是一组串行执行的Handler。

框架底层已经实现好了HandlerGroup的代码,我们只要把每个Handler实现即可(Handler的代码可以前面的例子)。
然后在配置文件中,配置好Handler的执行顺序:
name: handler_group_a
type: HandlerGroup
handlers: - ExampleAHandler- ExampleBHandler
应用
部分代码如下(完整见github)
package mainimport ("fmt""ghgroups/frame""ghgroups/frame/constructor"constructorbuilder "ghgroups/frame/constructor_builder""ghgroups/frame/factory"ghgroupscontext "ghgroups/frame/ghgroups_context""os""path""reflect"
)func main() {factory := factory.NewFactory()factory.Register(reflect.TypeOf(ExampleAHandler{}))factory.Register(reflect.TypeOf(ExampleBHandler{}))runPath, errGetWd := os.Getwd()if errGetWd != nil {fmt.Printf("%v", errGetWd)return}concretePath := path.Join(runPath, "conf")constructor := constructorbuilder.BuildConstructor(factory, concretePath)mainProcess := "handler_group_a"run(constructor, mainProcess)
}
这次对象构建器我们需要使用constructorbuilder.BuildConstructor去构建。因为其底层会通过配置文件所在的文件夹路径(concretePath )构建所有的组件。而在此之前,需要告诉构建器还有两个我们自定义的组件(ExampleAHandler和ExampleBHandler)需要注册到系统中。于是我们暴露出对象工厂(factory )用于提前注册。
运行结果如下
handler_group_aExampleAHandlerExampleBHandler
run ExampleAHandler
run ExampleBHandler
前三行是配置文件描述的执行流程。后两行是实际执行流程中的输出。
AsyncHandlerGroup
AsyncHandlerGroup是一组并行执行的Handler。

和HandlerGroup一样,框架已经实现了AsyncHandlerGroup的底层代码,我们只用实现各个Handler即可。
有别于HandlerGroup,它需要将配置文件中的type设置为AsyncHandlerGroup。
name: async_handler_group_a
type: AsyncHandlerGroup
handlers: - ExampleAHandler- ExampleBHandler
应用
使用的代码和HandlerGroup类似,具体见github。
执行结果如下
async_handler_group_aExampleAHandlerExampleBHandler
run ExampleBHandler
run ExampleAHandler
Layer
Layer由两部分组成:Divider和Handler。Handler是一组业务逻辑,Divider用于选择执行哪个Handler。

Divider
不同于Handler,Divider需要继承和实现DividerBaseInterface接口。
type DividerBaseInterface interface {ConcreteInterfaceSelect(context *ghgroupscontext.GhGroupsContext) string
}
Select方法用于填充业务逻辑,选择该Layer需要执行的Handler的名称。下面是Divider具体实现的一个样例(见github)。
package mainimport ("ghgroups/frame"ghgroupscontext "ghgroups/frame/ghgroups_context""reflect"
)type ExampleDivider struct {frame.DividerBaseInterface
}func NewExampleDivider() *ExampleDivider {return &ExampleDivider{}
}// ///
// ConcreteInterface
func (s *ExampleDivider) Name() string {return reflect.TypeOf(*s).Name()
}// ///
// DividerBaseInterface
func (s *ExampleDivider) Select(context *ghgroupscontext.GhGroupsContext) string {return "ExampleBHandler"
}
应用
每个Layer都要通过配置文件来描述其组成。相较于HandlerGroup,由于它不会执行所有的Handler,而是要通过Divider来选择执行哪个Handler,于是主要是新增Divider的配置项。
name: layer_a
type: Layer
divider: ExampleDivider
handlers: - ExampleAHandler- ExampleBHandler
具体执行的代码见github。我们看下运行结果
layer_aExampleDividerExampleAHandlerExampleBHandler
run ExampleBHandler
可以看到它只是执行了Divider选择的ExampleBHandler。
LayerCenter
LayerCenter是一组串行执行的Layer的组合。

在使用LayerCenter时,我们只要实现好每个Layer,然后通过配置文件配置它们的关系即可。
type: LayerCenter
name: layer_center
layers: - layer_a- layer_b
应用
具体代码和前面类似,可以见github。
运行结果如下
layer_centerlayer_aExampleADividerExampleA1HandlerExampleA2Handlerlayer_bExampleBDividerExampleB1HandlerExampleB2Handler
run ExampleA2Handler
run ExampleB1Handler
可以看到每个Layer选择了一个Handler执行。
源码见github。
相关文章:
使用Golang实现一套流程可配置,适用于广告、推荐系统的业务性框架——简单应用
在诸如广告、推荐等系统中,我们往往会涉及过滤、召回和排序等过程。随着系统业务变得复杂,代码的耦合和交错会让项目跌入难以维护的深渊。于是模块化设计是复杂系统的必备基础。这篇文章介绍的业务框架脱胎于线上多人协作开发、高并发的竞价广告系统&…...
一个.NET开发的Web版Redis管理工具
今天给大家推荐一款web 版的Redis可视化工具WebRedisManager,即可以作为单机的web 版的Redis可视化工具来使用,也可以挂在服务器上多人管理使用的web 版的Redis可视化工具。 WebRedisManager基于SAEA.Socket通信框架中的SAEA.RedisSocket、SAEA.WebApi两…...
javaAPI(四):jdk8中的日期时间API
新日期时间API出现的背景 jdk8之前时间日期API 如果我们可以跟别人说:“我们在1502653933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。jdk 1.0中包含了一个java.util.Date类&am…...
解决在mybatis中出现的org.apache.ibatis.exceptions.PersistenceException~
我在使用mybatis中的注解对数据库中的信息进行操作时,出现了下述错误 我在mapper接口中定义了该方法,并且使用注解绑定了对应的SQL语句 //增加用户信息 Insert("insert into user values(#{id},#{name},#{password})") int addUser(user user…...
Vue + ElementUI 实现可编辑表格及校验
效果 完整代码见文末 实现思路 使用两个表单分别用于实现修改和新增处理。 通过一个editIndex变量判断是否是编辑状态来决定是否展示输入框,当点击指定行的修改后进行设置即可: <el-table-columnv-for"(column, index) in columns":key&qu…...
中介者模式——协调多个对象之间的交互
1、简介 1.1、概述 如果在一个系统中对象之间的联系呈现为网状结构,如下图所示: 对象之间存在大量的多对多联系,将导致系统非常复杂,这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对…...
启动Flink显示初始化状态怎么解决?
启动Flink显示初始化状态怎么解决? Flink On Yarn模式 问题 flnk任务在跑的过程中, 有时候任务停掉了 ,不过我有 定时任务,可以把失败的flink任务拉起来,但是因为最新的checkpoint做失败了,导致脚本无法拉…...
VB+SQL采购管理系统设计与实现
摘 要 本系统是基于为轴承企业采购部门开发的系统。课题主要采用自上而下的结构化程序设计方法与面向对象方法相结合的方法,致力于达到标准的现代化物流管理要求。帮助轴承企业采购部门全面实现电子化、自动化、标准化的现代化先进管理模式。 该系统使用Visualbasic.net编程…...
TBB库中实现协程(coroutine)的源码说明
源码请见: https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h 在windows系统,TBB(也就是intel 的 oneTBB库),通过windwos fiber(纤程)来实现协程(coroutine)。 创建一个协程,代码很简洁: inline void create_coroutine(corouti…...
【CSS弹性盒模型 display:flex;常用参数及常见的布局】
CSS弹性盒模型 display:flex;常用参数flex-directionjustify-contentalign-itemsflex-wrapflex-flowalign-contentorderflex-growflex-shrinkflex-basis 常见的布局1. 水平居中2. 垂直居中3. 水平垂直居中4. 等分布局5. 响应式布局6. 网格布局 常见的布局封装 display:flex;常用…...
golang函数传参——值传递理解
做了五年的go开发,却并没有什么成长,都停留在了业务层面了。一直以为golang中函数传参,如果传的是引用类型,则是以引用传递,造成这样的误解,实在也不能怪我。我们来看一个例子,众所周知…...
Liunx环境下git的详细使用(gitee版)
Liunx环境下git的详细使用(gitee版) 1.git是什么2.git操作2.1在gitee创建一个仓库2.2.gitignore2.3.git 3.git三板斧3.1add3.2 commit3.3push 4.git其他命令4.1查看当前仓库状态4.2查看提交日志4.3修改git里面文件名称4.4删除文件4.5修改远端仓库内容 1.…...
exoplayer的使用-2,与flutter相互通信
上一次解决的问题是ac3这些exoplayer本身不支持音频,添加了ffmpeg扩展实现软解码. 另一个问题是flutter端的内置字幕显示不了,也不打算再继续探讨了,换成native实现播放器.由于主项目是flutter的,所以涉及到了相互通信. 当前文章主要涉及到flutter与native相互通信功能 通信有…...
【基础类】—类型转换
一、数据类型 原始类型 Boolean、Null、Undefined、Number、String、Symbol 对象 Object 二、显示类型转换 Number函数, Number(param) 1-1. param 是 原始类型 时 数值:转换后还是原来的值 字符串:如果可以被解析…...
【云原生】 一文了解docker中的网络
文章目录 1.在Docker中,网络的实现方式是通过使用不同的网络驱动程序来实现的。2.桥接网络是默认网络。3.主机网络模式是不使用网络隔离的一种特殊模式。4.另一个有用的网络驱动程序是覆盖网络。5.最后,MACVLAN网络驱动程序允许多个容器与宿主机网卡的单独MAC地址和IP地址之间…...
嵌入式开发学习(STC51-15-红外遥控)
内容 使用外部中断功能,使按下红外遥控器,将对应键值编码数据解码后通过数码管显示 红外遥控介绍 红外线简介 人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫; 其中红光的波长范围为 0.62&…...
代码编辑器实践之vue-codemirror使用
前言 程序员用到IDE次数比较频繁,比如vscode、idea等,这些都是市场上比较流行的代码编辑器,拥有非常全面的功能。但是有时候在项目开发上也会用到代码编辑器,比如复杂的Array<Object>输入,或者需要用到用户交互…...
Mapstruct
1. Mapstruct介绍 1. 实体类之间对象映射中间件 2. 实体类相同结构属性自动对象映射 3. 实体类不同结构属性可以手动配置对象映射 2. Mapstruct基本使用 1. 定义一个接口或抽象类加Mapper(componentModel “spring”);spring:可以spring框架注入 2. 定义…...
初阶C语言——特别详细地介绍函数
系列文章目录 第一章 “C“浒传——初识C语言(更适合初学者体质哦!) 第二章 详细认识分支语句和循环语句以及他们的易错点 第三章 初阶C语言——特别详细地介绍函数 目录 系列文章目录 前言 一、函数是个什么鬼东西? 二、C语…...
pulsar-client-1-2 PulsarClient构造函数
前言 上文说到,PulsarClient通过链式调用构建,而在build()中调用了new PulsarClientImpl(conf),而Producer 本文通过解析构造函数,了解其主要结构。 // 创建PulsarClient PulsarClient client = PulsarClient.builder().serviceUrl("pulsar://localhost:6650")…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
