Golang微服务中"删除通知邮件"功能 - 是否应该独立为一个微服务?

Golang微服务中"删除通知邮件"功能 - 是否应该独立为一个微服务? 大家好,

在工作中,我们编写了一个微服务,为项目创建提供REST API。它本质上是一个数据库表的CRUD封装。它管理自己的数据库表,但每当修改表中的某一行时,都需要调用一些卫星系统。这使得它已经相当庞大。

我们想要添加新功能:同一个微服务应该能够以API模式运行,即暴露其REST API,同时也能够以命令行模式运行。

在命令行模式下,运行它会向每个即将在12天、6天或1天内到期的项目所有者发送一封“删除通知邮件”。如果我使用某个特定标志运行它,它将只向单个项目发送一封邮件。

为了实现这个“删除通知邮件”功能,我查询即将到期的项目,给它们的所有者发送邮件,如果任何邮件发送失败,我会调用另一个卫星系统。

将一个微服务用于REST API(主要进行CRUD操作),而另一个独立的微服务由cronjob定期触发并执行邮件查询和发送,这样做有意义吗?

如果我们将这两个功能保留在一个更大的单一微服务中,并以CLI模式或API模式调用它,会有什么好处吗?


更多关于Golang微服务中"删除通知邮件"功能 - 是否应该独立为一个微服务?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

两种使用模式以略有不同的方式处理相同的数据。如果这两种方式共享数据的业务规则并处理相同的领域对象,我认为将它们保留在一个内聚的服务中是可行的。

拆分服务总是伴随着成本:服务间额外的通信、API变更、部署和配置复杂性的增加。这些成本不应被低估。

我建议参考领域驱动设计(DDD)来设计服务。如果两种使用模式可以合理地归入同一个限界上下文,这强烈表明应将它们保留在单一服务中。

或许你可以尝试这样设计你的服务:让 REST 和 CLI 模式都只是围绕共同的业务对象和规则核心的薄包装层。

更多关于Golang微服务中"删除通知邮件"功能 - 是否应该独立为一个微服务?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在微服务架构中,将“删除通知邮件”功能独立为一个专门的微服务是更符合设计原则的做法。这有助于遵循单一职责原则,提高系统的可维护性和可扩展性。以下是我的分析和示例代码:

1. 独立微服务的优势

  • 职责分离:主微服务专注于CRUD操作,邮件服务专注于通知逻辑。
  • 独立部署和扩展:邮件服务可以独立于主服务进行部署、扩展和更新。
  • 容错性:邮件发送失败不会直接影响主服务的核心功能。
  • 技术异构性:邮件服务可以选择更适合邮件处理的框架或库。

2. 示例代码结构

主微服务(项目CRUD)

// main_service/main.go
package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/projects", createProject).Methods("POST")
    r.HandleFunc("/projects/{id}", updateProject).Methods("PUT")
    r.HandleFunc("/projects/{id}", deleteProject).Methods("DELETE")
    
    http.ListenAndServe(":8080", r)
}

func createProject(w http.ResponseWriter, r *http.Request) {
    // 创建项目逻辑
    // 调用邮件服务的API发送创建通知
    notifyEmailService("project_created", projectData)
}

邮件微服务

// email_service/main.go
package main

import (
    "context"
    "encoding/json"
    "net/http"
    "time"
    
    "github.com/gorilla/mux"
    "gopkg.in/gomail.v2"
)

type EmailRequest struct {
    Type    string      `json:"type"`
    Project ProjectData `json:"project"`
}

func main() {
    // API模式:接收邮件发送请求
    r := mux.NewRouter()
    r.HandleFunc("/send-notification", sendNotification).Methods("POST")
    
    // CLI模式:定时任务执行
    go runCronJob()
    
    http.ListenAndServe(":8081", r)
}

func sendNotification(w http.ResponseWriter, r *http.Request) {
    var req EmailRequest
    json.NewDecoder(r.Body).Decode(&req)
    
    switch req.Type {
    case "deletion_warning":
        sendDeletionWarning(req.Project)
    case "project_created":
        sendCreationNotification(req.Project)
    }
}

func runCronJob() {
    ticker := time.NewTicker(24 * time.Hour) // 每天执行一次
    for range ticker.C {
        checkAndSendDeletionWarnings()
    }
}

func checkAndSendDeletionWarnings() {
    // 查询即将到期的项目
    projects := queryExpiringProjects()
    
    for _, project := range projects {
        // 计算剩余天数
        daysLeft := calculateDaysLeft(project.ExpiryDate)
        
        if shouldSendWarning(daysLeft) {
            sendDeletionWarning(project)
        }
    }
}

func sendDeletionWarning(project ProjectData) error {
    m := gomail.NewMessage()
    m.SetHeader("From", "noreply@example.com")
    m.SetHeader("To", project.OwnerEmail)
    m.SetHeader("Subject", "项目删除通知")
    m.SetBody("text/html", generateWarningEmail(project))
    
    d := gomail.NewDialer("smtp.example.com", 587, "user", "password")
    
    if err := d.DialAndSend(m); err != nil {
        // 邮件发送失败,调用卫星系统
        callSatelliteSystem("email_failed", project)
        return err
    }
    
    return nil
}

命令行工具

// email_service/cli.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    singleProject := flag.String("project", "", "发送单个项目的通知邮件")
    flag.Parse()
    
    if *singleProject != "" {
        // 发送单个项目的通知
        project := getProject(*singleProject)
        sendDeletionWarning(project)
    } else {
        // 批量发送所有到期项目的通知
        checkAndSendDeletionWarnings()
    }
}

3. 部署配置

# docker-compose.yml
version: '3'
services:
  main-service:
    build: ./main_service
    ports:
      - "8080:8080"
    environment:
      - EMAIL_SERVICE_URL=http://email-service:8081

  email-service:
    build: ./email_service
    ports:
      - "8081:8081"
    command: ["./email-service", "cron"]  # 或 "api" 模式

4. 通信方式

  • 主服务通过HTTP API调用邮件服务
  • 邮件服务通过数据库查询获取到期项目信息
  • 使用消息队列(如RabbitMQ)进行异步通信可进一步提高解耦

这种分离设计使得每个服务都可以独立开发、测试、部署和扩展。邮件服务的失败不会影响主服务的核心CRUD功能,同时邮件服务可以根据需要单独扩展以处理大量邮件发送任务。

回到顶部