使用Golang实现一套流程可配置,适用于广告、推荐系统的业务性框架——简单应用

news2024/11/18 19:55:48

在诸如广告、推荐等系统中,我们往往会涉及过滤、召回和排序等过程。随着系统业务变得复杂,代码的耦合和交错会让项目跌入难以维护的深渊。于是模块化设计是复杂系统的必备基础。这篇文章介绍的业务框架脱胎于线上多人协作开发、高并发的竞价广告系统,在实践中不停被优化,直到易于理解和应用。

基础组件

Handler

在系统中,我们定义一个独立的业务逻辑为一个Handler。
比如过滤“机型”信息的逻辑可以叫做DeviceFilterHandler,排序的逻辑叫SortHandler。
Handler的实现也很简单,只要实现frame.HandlerBaseInterface接口和它对应的方法即可(见github):

package main

import (
	"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 {
	ConcreteInterface
	Handle(context *ghgroupscontext.GhGroupsContext) bool
}

因为HandlerBaseInterface 继承自ConcreteInterface ,所以我们只要让自己构建的Handler继承自HandlerBaseInterface,并实现相应方法即可。

应用

一般一个复杂的业务不能只有一个Handler,但是为了便于方便讲解,我们看下怎么运行只有一个Handler的框架(见github)。

package main

import (
	"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 = true
			mainHandlerGroup.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 main

import (
	"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_a
        ExampleAHandler
        ExampleBHandler
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_a
        ExampleAHandler
        ExampleBHandler
run ExampleBHandler
run ExampleAHandler

Layer

Layer由两部分组成:Divider和Handler。Handler是一组业务逻辑,Divider用于选择执行哪个Handler。
在这里插入图片描述

Divider

不同于Handler,Divider需要继承和实现DividerBaseInterface接口。

type DividerBaseInterface interface {
	ConcreteInterface
	Select(context *ghgroupscontext.GhGroupsContext) string
}

Select方法用于填充业务逻辑,选择该Layer需要执行的Handler的名称。下面是Divider具体实现的一个样例(见github)。

package main

import (
	"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_a
        ExampleDivider
        ExampleAHandler
        ExampleBHandler
run ExampleBHandler

可以看到它只是执行了Divider选择的ExampleBHandler。

LayerCenter

LayerCenter是一组串行执行的Layer的组合。
在这里插入图片描述
在使用LayerCenter时,我们只要实现好每个Layer,然后通过配置文件配置它们的关系即可。

type: LayerCenter
name: layer_center
layers: 
  - layer_a
  - layer_b

应用

具体代码和前面类似,可以见github。
运行结果如下

layer_center
        layer_a
                ExampleADivider
                ExampleA1Handler
                ExampleA2Handler
        layer_b
                ExampleBDivider
                ExampleB1Handler
                ExampleB2Handler
run ExampleA2Handler
run ExampleB1Handler

可以看到每个Layer选择了一个Handler执行。

源码见github。

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

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

相关文章

java系列之list集合分组

文章目录 前言一、list是什么?二、list集合分组总结 前言 在Java编程中,List集合是一种常用的数据结构,用于存储一组元素。有时候,我们需要对List集合中的元素进行分组操作,即将相同属性或特征的元素归类到一组。这种…

金三银四好时节,python面试10K+能不能得到?

嗨害大家好鸭!我是小熊猫~ 金三银四好时节,面试10K能不能得到? 这次正逢面试季,这次给大家带来一个真实面试题 虽然最后上的班不一定是自己喜欢的, 但是工作还是要有哇! 第三方库: requests >>>…

【C++杂货铺】string使用指南

文章目录 前言一、介绍二、string类的常用接口说明2.1 常见的构造接口2.2 与容量有关的接口2.3 与对象访问及遍历有关的操作2.4 与对象修改有关的操作2.5 与查找有关的接口2.6 string类的非成员函数2.7 与类型转换有关的接口 前言 在C语言中,字符串是以\0结尾的一些…

Win10、11集体翻车,祖传Bug连关机都不能

基本可以确定的是,微软将在2024年推出或名为 Win12 的下一代 Windows 。 Win10、11 的更新跨度肉眼可见放缓,可频率嘛,仍然一月几更。 如果你没有禁用更新,应该和小蝾一样,下班关机时经常有熟悉的选项。 不过在大多数…

分享之python 线程

Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。 1、threading模块 threading 模块建立在 _thread 模块之上。thread 模块以低级、原始的方式来处理和控制线程,而 threading 模块通过对 thread 进行二次封装,提供了更方…

ansible常见模块的运用

ansible常见模块的运用 一:Ansible简介二:ansible 环境安装部署管理端安装 ansibleansible 目录结构配置主机清单配置密钥对验证 三:ansible 命令行模块1.command 模块在远程主机执行命令,不支持管道,重定向…

这所国字头双一流,根本招不满,学硕都没人报!

一、学校及专业介绍 中国民航大学,位于天津市,是民航局、天津市、教育部共建高校,是天津市“双一流”建设高校和高水平特色大学建设高校。 1.1 招生情况 2023年中国民航大学电子信息与自动化学院,初试考806信号与系统的一共有两…

Windows环境下创建定时任务执行Python脚本

一、环境 以下演示使用的环境说明 Windows 操作系统:Win10 Python 环境:Python 3.7.7 二、脚本 准备Python脚本如下: import sys import datetimeformat_time datetime.datetime.now()with open(file"forpy.log",mode"a&qu…

单月涨粉345万,7月抖音爆款技巧是什么?

近期,抖音电商官方发布「七夕好礼季」的玩法攻略,助力商家抢跑七夕。如今,各类营销节点,成为商家实现生意爆发的关键。 那么,在没有营销节点加持的7月,那些表现突出的主播,持续畅销的商品&#…

解决elementUI图标按钮调整宽高后图标没有居中的问题

解决elementUI图标按钮调整宽高后图标没有居中的问题 1.情景再现2.解决2.1 重新设置新的padding2.2 flex布局 1.情景再现 我是用elementui的图标按钮组件,但是我想要的大小和官方给到的大小不一致。所以我给图标设置了一个width: 16px;height: 16px;的属性值&#…

开学在即,这个超好用的中小学新生录取查询系统制作方法值得借鉴

即将开学,中小学负责招生的老师面临着新学年的招生工作。这是一项紧迫且重要的任务,需要老师们迅速而有效地应对。在新生录取过程中,有几个关键任务需要尽快完成。 首先,老师们需要录入新生的成绩信息。这包括学生的考试成绩、综…

算法通关村—二叉树处理每层元素的题目

1. 在每个树行中找最大值 给定一棵二叉树的根节点 root &#xff0c;请找出该二叉树中每一层的最大值。 输入: root [1,3,2,5,3,null,9] 输出: [1,3,9] 依然先写出层序遍历&#xff0c;只不过遍历的过程中需要保存当前一层的最大值。 public List<Integer> largestVa…

VLT:Vision-Language Transformer用于引用的视觉语言转换和查询生成分割

摘要 在这项工作中&#xff0c;我们解决了引用分割的挑战性任务。引用分割中的查询表达式通常通过描述目标对象与其他对象的关系来表示目标对象。因此&#xff0c;为了在图像中的所有实例中找到目标实例&#xff0c;模型必须对整个图像有一个整体的理解。为了实现这一点&#…

【Java】Springboot脚手架生成初始化项目代码

Springboot配置生成初始化项目代码可以通过mvn的mvn archetype:generate 和阿里云原生应用脚手架&#xff08;地址&#xff09;、spring官方提供的start初始化生成页面(地址&#xff09;。 1、mvn archetype:generate 通过mvn选择对应的脚手架可以快速生成初始化代码&#xf…

【网络安全】网络安全威胁实时地图 - 2023

文章目录 [TOC] ① 360 安全大脑360 APT全景雷达 ② 瑞星杀毒瑞星云安全瑞星网络威胁态势感知平台 ③ 比特梵德 Bitdefender④ 飞塔防火墙 FortiGuard⑤ 音墙网络 Sonicwall⑥ 捷邦 Check Point⑦ AO卡巴斯基实验室全球模拟隧道模拟 ⑧ 数字攻击地图⑨ Threatbutt互联网黑客攻击…

Ansys Lumerical | GPU,超透镜,铌酸锂调制器等重磅来袭!

Ansys Lumerical 2023R2新版本正式发布&#xff01;主要集中在光子学多物理场求解器增强&#xff0c;FDTD GPU 加速支持&#xff0c;超透镜流程优化&#xff0c;铌酸锂调制器支持&#xff0c;光子集成电路仿真能力增强&#xff0c; GUI增强和云计算支持等。 光子学核心技术 1、…

【Clion 2】多行TODO使用

一、TODO: 说明&#xff1a; 有时需要标记部分代码以供将来参考&#xff1a; 优化和改进的领域、可能的更改、要讨论的问题等等。 支持&#xff1a; TODO和FIXME小写和大写。这些模式可以在任何受支持的文件类型的行注释和块注释内使用。 创建TODO项 在要添加注释的代码行中…

问道管理:房地产政策调控新信号出现,南京有二手房东连夜跳价100万

最近几天&#xff0c;国家发改委、住建部、北上广深住建部门表态挺楼市&#xff0c;放宽房地产方针。一夜之间&#xff0c;地产股大涨&#xff0c;多地商场情绪恢复。 诸葛找房数据显现&#xff0c;自7月29日起&#xff0c;南京万科金域缇香、紫园、后标营、翠屏诚园等小区的部…

云道资本:2023中国氢能源产业-氢制备深度研究报告(附下载)

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 中国可再生能源消纳能力提升远远滞后于发电占比的提升。大规模的可再生能源发电是实现碳中和的关键一步&#xff0c;但风电、光伏发电间歌性、波动性强&#xff0c;电网消纳压力较大&#xff0c;且电…

【图论】强连通分量进阶

一.作用 强连通分量可以判断环和进行缩点。还有一系列作用.... 这篇文章介绍缩点 二.题目 https://www.luogu.com.cn/problem/P2341 三.思路 我们分析可以知道当一个点没有出度时&#xff0c;则为最受欢迎的牛。但如果有多个出度&#xff0c;则没有最受欢迎的牛。 这是只有…