首页 > 其他分享 >A Practical Methodology, HSM, Handler,Service,Model, for Golang Backend Development

A Practical Methodology, HSM, Handler,Service,Model, for Golang Backend Development

时间:2023-06-20 11:14:04浏览次数:42  
标签:Development HTTP Service err pattern HSM Golang service logic

Everybody is familiar with the widely adopted MVC (Model-View-Controller) pattern, which has been used for many years across various languages and frameworks. MVC has proven to be a practical pattern for organizing programs with user interfaces and multiple object models.   In this article, I would like to introduce a simple methodology or design pattern called HSM (Handler, Service, Model) or Golang backend development. HSM is similar to MVC but specifically tailored for pure backend development in the Go programming language (Golang).   The HSM pattern provides a clear and organized structure for developing backend applications using Golang. Let's explore its three key components:  

 

In a nutshell

Handler

The Handler component in HSM serves as the entry point for incoming requests. It handles the routing and request/response handling. Handlers are responsible for receiving requests from clients and orchestrating the flow of data between the service layer and the external world. They act as a bridge between the client and the underlying application logic. For example, HTTP, gRPC, WebSocket, JSONRPC, XMLRPC, TCP, UDP etc.

Service

The Service component encapsulates the business logic of the application. It handles the processed input, translated from the handlers. Services are responsible for executing complex operations, interacting with databases or external APIs, and implementing the core functionality of the application. They are designed to be modular and reusable, promoting code maintainability and separation of concerns.

Model

The Model component represents the data structures and domain-specific entities used in the application. It includes the data access layer responsible for interacting with databases or other persistent storage. Models in HSM are designed to represent the application's data schema and provide methods for data retrieval, manipulation, and storage.  

 

By adopting the HSM pattern in Golang backend development, you can achieve several benefits:
  1. Improved organization: HSM provides a clear separation of concerns, making it easier to understand and maintain the codebase. Each component has a distinct responsibility, making the codebase more modular and scalable.
  2. Testability: With the separation of concerns, each component can be tested independently, enabling comprehensive testing of the application's functionality. Mocking and stubbing can be utilized effectively to isolate components during testing.
  3. Code reusability: The modular design of HSM allows for the reuse of components across different parts of the application. Handlers, services, and models can be shared and composed to build new features or extend existing functionality, reducing duplication of code and promoting code reuse.
  4. Flexibility: The HSM pattern provides flexibility in terms of adapting to changing requirements. As the application evolves, components can be modified or added without affecting the entire codebase, making it easier to accommodate new features or adjust the existing behavior.
Generally speaking, when handling requests, they are often wrapped in different protocols and need to be interpreted by the program before the actual logic can be executed. This necessitates certain considerations:
  • Separation of protocol-related request and response from the business logic.
  • Processing of business logic involving multiple object models.
  • Returning a response based on the data or error from the previous steps.
In the case of a backend that processes HTTP requests, a Golang program typically follows these steps: For a backend which processes HTTP request, the Golang program will have to
  1. Translating the HTTP request into one or multiple inputs for services.
  2. Invoking one or multiple services to generate output and handle any errors.
  3. Processing the service output along with errors and returning an appropriate HTTP response.
One common mistake in Golang programs is writing business logic directly in the HTTP handler or around specific protocols. This approach leads to code that cannot be easily reused later on and is challenging to test efficiently. By introducing the concept of services, inputs, and outputs, complex logic can be organized sequentially, and data flow can be monitored within unit tests without relying on a mock HTTP server, using only a local database. Let me illustrate the HSM methodology with an example. Consider the scenario where we need to develop an HTTP server that accepts user requests to create a book and specify whether they liked it or not. Declare models firstly.
type Book struct {
	gorm.Model

	Name string
}

type BookLike struct {
	gorm.Model

	BookID uint
}
Create the Golang service.
type CreateBookInput struct {
    Name string
    Like bool
}

type CreateBookOutput struct {
    ID uint `json:"id"`
}

type ServiceInterface interface {
    CreateBook(context.Context, *CreateBookInput) (*CreateBookOutput, error)
}

type Service struct {
    DB *gorm.DB
}

func (s *Service) CreateBook(ctx context.Context, in *CreateBookInput) (*CreateBookOutput, error) {
    book := Book{
        Name: in.Name,
    }
    err := s.DB.Transaction(func(tx *gorm.DB) error {
        err := tx.Create(&book).Error
        if err != nil {
            return err
        }
        if in.Like {
            err = tx.Create(&BookLike{BookID: book.ID}).Error
            if err != nil {
                return err
            }
        }
        return nil
    })
    if err != nil {
        return nil, err
    }

    return &CreateBookOutput{
        ID: book.ID,
    }, nil
}
As we can observe, the service related to books, specifically the CreateBook method, handles interactions with both object models. However, it focuses solely on receiving input and generating an output with an error. After implementing the necessary service methods, we are now ready to invoke them from the HTTP handler.
type CreateBookRequest struct {
    Name string `json:"name"`
    Like bool   `json:"like"`
}

type CreateBookResponse struct {
    ID uint `json:"id"`
}

type Server struct {
    Service ServiceInterface
}

func (s *Server) CreateBookHandler(w http.ResponseWriter, r *http.Request) {
    // Parse request.
    var req CreateBookRequest
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    // Call service with input and output.
    out, err := s.Service.CreateBook(r.Context(), &CreateBookInput{
        Name: req.Name,
        Like: req.Like,
    })
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // Create response from output from last step.
    res := CreateBookResponse{
        ID: out.ID,
    }

    // Write response.
    resJSON, _ := json.Marshal(&res)
    w.Write(resJSON)
}

// Create the server with service.
func main() {
    // Open GORM database.
    db, _ := gorm.Open()

    // Create service with all clients it needs.
    svc := Service{DB: db}

    // Create the HTTP handler and serve it.
    srv := Server{Service: &svc}
    http.ListenAndServe(":8080", srv)
}
This is a typical HTTP handler responsible for handling requests and generating responses. The structure of the HTTP request and response can be automatically generated using tools like oapi-gen or OpenAPI generator, based on your preferences. Now, let's consider the deployment of this book service as a microservice on AWS Lambda. The resulting program would look like the following:
var svc ServiceInterface

type CreateBookEvent struct {
    Name string `json:"name"`
}

func HandleLambdaRequest(ctx context.Context, evt CreateBookEvent) (string, error) {
    out, err := svc.CreateBook(ctx, &CreateBookInput{
        Name: evt.Name,
    })
    if err != nil {
        return "", err
    }
    outJSON, err := json.Marshal(out)
    return string(outJSON), err
}

func main() {
    db, _ := gorm.Open()
    svc = &Service{DB: db}
    lambda.Start(HandleLambdaRequest)
}
Nearly the same right.   By organizing all the business logic into a concept called "service," we can easily encapsulate complex logic without coupling it to any specific protocol. This means that the book service we have developed can be fully reused. By adding the necessary glue code for a specific protocol or framework, the new program can be deployed instantly without altering the underlying logic. The service can also contain interfaces to other services, and the handler structure can include a list of services for cohesive functionality.   Now, let's consider another common pattern found in web development. However, most of these patterns fail to address a fundamental problem: how to efficiently handle complex business logic that involves multiple object models and write reusable code.   The straightforward solution to this challenge is the repository-based pattern, which is inspired by the classical Java DAO (Data Access Object) pattern. When dealing with numerous object models or when the program is in its early stages, writing and constantly changing duplicated interfaces can become time-consuming. The repository pattern aims to centralize single model access logic, not many or complicated.   However, in practice, bugs tend to reside within the business logic code rather than in the repository or database IO. Furthermore, repositories can be overly simplistic, and writing unit tests for them may be unnecessary at the early stages of development.   The more complex the repository code becomes, the more effort is required to access the objects themselves. In the context of the program, accessing object models is often the most critical part of the business logic. When objects are retrieved from the database, the actual computation begins immediately, and the results are written back to the database. As SQL queries become more intricate, or when multiple object models need to be handled within a transaction, or when database access optimization is necessary, the repository pattern can become a bottleneck within the program. This often leads to the repository classes being replaced by direct object access, which is where the HSM pattern comes into play.   This article aims to provide valuable insights for your Golang backend development by introducing the HSM pattern as a solution to the challenges of handling complex business logic efficiently.

标签:Development,HTTP,Service,err,pattern,HSM,Golang,service,logic
From: https://www.cnblogs.com/Jedimaster/p/17493053.html

相关文章

  • gjson - Golang 解析 JSON
    文章目录简介主要类型TypeResult方法gjsonresultPath修饰符示例介绍自定义备用简介Github地址go安装:goget-ugithub.com/tidwall/gjson主要类型Type说明说明:解析的数据类型(实际是int类型)功能:用于解析和输出时做判断包括:-True-False-String-JSON-Number......
  • Golang - kafka 的使用
    producerpackagemainimport( "fmt" "github.com/Shopify/sarama" "log" "strconv")const( BROKER="ip:port" TOPIC="xx")//sendMsg发送到kfkfuncsendMsg(clientsarama.SyncProducer,ms......
  • Golang - net/http 笔记
    Serverpackagemainimport( "fmt" "log" "net/http")//模拟实现Handler接口typeBarstruct{}func(bBar)ServeHTTP(whttp.ResponseWriter,req*http.Request){ tgt:=req.URL.Query().Get("name") fmt.Fprintf(w,......
  • Golang - 日志
    官方Log包方法输出到logger.out:log.Print(),log.Printf(),log.Println()输出到logger.out,再执行os.Exit(1):log.Fatal(),log.Fatalln(),log.Fatalf()输出到logger.out,再执行panic():log.Panic(),log.Panicln(),log.Panicf()logger结构体typeLoggerstruc......
  • Golang - Structs 包的使用
    packagemain////主要用于struct转map//还可以判断结构体是否有空属性等功能//import( "fmt" "github.com/fatih/structs")//struct-->maptypeStustruct{ Namestring Ageint}funcmain(){ //创建一个Age属性为空的struct实例 u1:=Stu{......
  • Android AccessibilityService 事件分发原理
    在了解了无障碍服务基础使用之后,我们来探究一下AccessibilityService的事件接收方法回调的时机和它深层次的实现逻辑。AccessibilityService监听事件的调用逻辑AccessibilityService有很多用来接收外部调用事件变化的方法,这些方法封装在内部接口Callbacks中:publicinterface......
  • 2023跟我一起学设计模式:Golang 抽象工厂模式讲解和代码示例
    Golang抽象工厂模式讲解和代码示例抽象工厂是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。抽象工厂定义了用于创建不同产品的接口,但将实际的创建工作留给了具体工厂类。每个工厂类型都对应一个特定的产品变体。在创建产品时,客户端代码调用的是工厂对象的......
  • springboot中自定义注解在service方法中,aop失效
    问题描述写了个自定义注解,但是该注解只会出现在serviece层中的方法中。启动发现aop未拦截到问题原因:调用service中的xx()方法时,Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他$XxxxService。所以调用xx()方法实际上是代理对象$XxxxService调用的。但是在xx()方法内调用同......
  • ServiceAccount 访问API实验
    概念SA账号是Pod内的进程使用的关联服务账号的身份,向集群的API服务器进行身份认证。SA(服务账号)是针对运行在Pod中的应用进程而言的,在Kubernetes中这些进程运行在容器中,而容器是Pod的一部分配置SAapiVersion:v1kind:ServiceAccountmetadata:name:sa-testnames......
  • golang之context
    context用来解决goroutine之间退出通知、元数据传递的功能。 context使用起来非常方便。源码里对外提供了一个创建根节点context的函数:funcBackground()Context background是一个空的context,它不能被取消,没有值,也没有超时时间。有了根节点context,又提供了四个函数创......