引言:为什么架构不是奢侈品,而是必需品
\n\n当你刚开始用Go编程时,一切似乎都很简单:goroutine飞驰,通道嗡嗡作响,编译器速度快得令人愉悦。但一两个月过去后,你的个人项目就变成了一个包含3000行package main的怪物,你只能祈祷“千万别崩溃”。熟悉吗?
Go提供了强大的工具,但并不强制设定严格的框架。这既是优点也是缺点。没有清晰的架构,你很快就会在自己的goroutine中迷失方向。正是在这里,Go语言中的架构模式——经过验证的典型设计问题解决方案——前来相助。
\n\n在本文中,我们将分析四个真正能简化生活的关键模式:Repository、Service Layer、Worker Pool和Pipeline。你将看到代码示例,了解何时应用哪种模式,并能立即将它们应用到自己的项目中。开始吧!
\n\n1. Repository模式:将业务逻辑与数据库分离
\n\n每个Go开发者应该掌握的第一个模式是Repository。其本质很简单:在业务逻辑和数据存储(数据库、文件、外部API)之间创建一个中间层。
\n\n为什么需要它?
\n- \n
- 可测试性:可以轻松地将真实数据库替换为模拟对象。 \n
- 灵活性:从PostgreSQL迁移到MongoDB?只需更改仓库。 \n
- 代码整洁:处理程序不知道数据来自哪里——这是仓库的任务。 \n
实现示例
\n假设我们有一个用户模型:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n}\n\n定义仓库接口:
\n\ntype UserRepository interface {\n GetByID(ctx context.Context, id int64) (*User, error)\n Create(ctx context.Context, user *User) error\n Update(ctx context.Context, user *User) error\n Delete(ctx context.Context, id int64) error\n}\n\n以及它在PostgreSQL上的实现:
\n\ntype PostgresUserRepo struct {\n db *sql.DB\n}\n\nfunc (r *PostgresUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {\n user := &User{}\n err := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id).\n Scan(&user.ID, &user.Name, &user.Email)\n if err != nil {\n return nil, fmt.Errorf("get user by id: %w", err)\n }\n return user, nil\n}\n\n现在,你的处理程序或服务只与UserRepository接口交互,而不是与特定的数据库。这就是Go语言中的Repository架构模式的实际应用。
2. Service Layer:业务逻辑尽在掌控
\n\n如果说Repository负责数据,那么Service Layer则负责业务规则。这个模式将分散的操作整合为有意义的动作。
\n\n没有Service Layer的问题
\n我经常看到这样的代码:处理程序包揽一切:验证请求、检查权限、写入数据库并记录日志。这违反了单一职责原则(SRP)。
\n\n如何解决?
\n引入服务层:
\n\ntype UserService interface {\n Register(ctx context.Context, name, email string) (*User, error)\n GetProfile(ctx context.Context, userID int64) (*User, error)\n}\n\ntype userService struct {\n repo UserRepository\n mail MailSender\n}\n\nfunc (s *userService) Register(ctx context.Context, name, email string) (*User, error) {\n // 1. 验证\n if name == "" || email == "" {\n return nil, ErrInvalidInput\n }\n\n // 2. 业务逻辑:检查邮箱是否已被占用\n existing, _ := s.repo.GetByEmail(ctx, email)\n if existing != nil {\n return nil, ErrEmailAlreadyExists\n }\n\n // 3. 创建用户\n user := &User{Name: name, Email: e