golang使用高阶函数优化业务功能

业务描述

        两个接口(新增Tag和更新Tag),在业务层均需要添加两个校验,校验Tag信息是否重复和Tag的数据中的编码是否重复。

基本实现

方式

        对应的增加两个校验的函数/方法,在接口实现中依次调用两个函数/方法进行校验。

优缺点

        实现简单;但重复代码多,后期再增加其他校验,扩展性较差。

高阶函数方式一

方式

        因为业务方法参数相同,业务层新增一个方法,包含一个函数类型参数(该函数即最终业务函数,新增/更新Tag),业务层新增方法:

func (t *TagUseCase) ValidateTag(ctx context.Context, req *Tag, f func(context.Context, *Tag) error) error {
   //校验一
   exists, err := t.repo.ValidateAppIdFieldExists(ctx, req.AppId, req.Field, req.IdString)
   if err != nil {
      return err
   }
   if exists {
      return errors.New(fmt.Sprintf("已存在:%v", req.Field))
   }
   //校验二
   flag, code := t.ValidateCodeUnique(req.Children)
   if flag {
      return errors.New(fmt.Sprintf("编码重复:%v", code))
   }
   //执行业务目标函数
   return f(ctx, req)
}

服务层调用:

func (s *TagService) CreateTag(ctx context.Context, req *pb.CreateTagRequest) (*pb.OperationTagReply, error) {
    //s.tuc.CreateTag为目标函数
	err := s.tuc.ValidateTag(ctx, tag, s.tuc.CreateTag)
	if err != nil {
		return nil, errors.New(0, err.Error(), "failed!")
	}
	return &pb.OperationTagReply{Msg: "success"}, nil
}

优缺点

        通过将目标函数参数化,将校验抽取到了一个方法中,后期如果增加其他校验,只需修改ValidateTag方法即可,有点类似于Java中的静态代理。重复代码很少,扩展性较好。但如果不同的业务需要的校验不完全相同,则存在问题。

高阶函数方式二

方式

        通过中间件实现(借鉴Kratos框架middleware),校验函数和目标函数的入参和返回值均相同(即使不同,可通过golang的闭包将函数包装成需要的函数签名),将其都作为一次处理,将所有的处理链接起来再执行。

定义中间件Handler类型:

定义中间件
type Handler func(ctx context.Context, req interface{}) error  #定义handler类型,即定义中间件和最终执行方法/函数的的声明
type Middleware func(Handler) Handler   # 定义Middle类型,即入参和返回值为相同类型的Handler,用于后面将其链接起来
# 将各中间件链接起来,next即最终要执行的函数,通过反向遍历的方式,将中间件按照添加的顺序依次链接,最先添加的在最外层,最先执行,结构类似:func(func(func(next)))
func Chain(m ...Middleware) Middleware {
   return func(next Handler) Handler {
      for i := len(m) - 1; i >= 0; i-- {
         next = m[i](next)
      }
      return next
   }
}

在业务层创建中间件:

func (t *TagUseCase) ValidatorTagExists() tagmiddleware.Middleware {
   return func(handler tagmiddleware.Handler) tagmiddleware.Handler {
      return func(ctx context.Context, req interface{}) (err error) {
         if v, ok := req.(*Tag); ok {
            exists, err := t.repo.ValidateAppIdFieldExists(ctx, v.AppId, v.Field, v.IdString)
            if err != nil {
               return err
            }
            if exists {
               return errors.New(fmt.Sprintf("该标签field已存在:%v", v.Field))
            }
         }
         return handler(ctx, req)
      }
   }
}

调用方式一:

#添加中间件:在业务层的结构体增加一个middleware字段,创建结构体时,将需要的中间件添加到该属性中
t.middleware = []tagmiddleware.Middleware{t.ValidatorTagExists(), t.ValidatorTagCodeRepeat()}

#提供给需要中间件的方法/函数调用: Chain首先获取到执行链,最终传入h进行调用
func (t *TagUseCase) Middleware(h tagmiddleware.Handler) tagmiddleware.Handler {
   return tagmiddleware.Chain(t.middleware...)(h)
}
最终调用:首先构建中间件,UpdateTag是最终要执行的方法。middleware(ctx,tag) 执行中间件和目标方法
middleware := s.tuc.Middleware(func(ctx context.Context, disposeReq interface{}) error {
   return s.tuc.UpdateTag(ctx, disposeReq.(*biz.Tag))
})
err = middleware(ctx, tag)

调用方式二:方式一提前指定了要执行那些中间件,不够灵活

#最终调用:在调用时指定要执行那些中间件。
middlewarex := tagmiddleware.Chain([]tagmiddleware.Middleware{s.tuc.ValidatorTagExists(), s.tuc.ValidatorTagCodeRepeat()}...)(func(ctx context.Context, disposeReq interface{}) error {
			return s.tuc.UpdateTag(ctx, disposeReq.(*biz.Tag))
		})
err = middlewarex(ctx, tag)

优缺点

        将校验和目标函数都作为handler处理,在调用时可以自定义设置要进行那些校验,灵活性高。但后期增加新的校验时,需要在多个调用的位置将新的校验handler添加到执行链中。文章来源地址https://uudwc.com/A/AA3yV

原文地址:https://blog.csdn.net/q79030/article/details/132969206

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

h
上一篇 2023年09月23日 19:12
C# webapi接口调用实例
下一篇 2023年09月23日 19:13