跳到主要内容

微服务不是银弹

微服务基础概念

1. 什么是"银弹思想"?为什么说微服务不是银弹?

面试官考察点: 对微服务架构的理性认识,避免盲目跟风

参考答案: 银弹思想是指认为存在某种技术可以解决软件工程中所有问题的思想。微服务不是银弹,因为:

  1. 复杂性转移:微服务将单体应用的复杂性转移到了分布式系统的复杂性
  2. 适用场景有限:不是所有项目都适合微服务架构
  3. 团队要求高:需要团队具备分布式系统开发和运维能力
  4. 基础设施要求:需要完善的DevOps、监控、服务治理能力

2. 在Go中,你会如何设计一个微服务的基础架构?

面试官考察点: Go微服务设计能力,技术选型

参考答案:

// 基础微服务结构
type Service struct {
server *http.Server
db *sql.DB
redis *redis.Client
logger *logrus.Logger
config *Config
}

func (s *Service) Start() error {
// 初始化中间件
s.setupMiddleware()
// 注册路由
s.setupRoutes()
// 服务注册
s.registerService()

return s.server.ListenAndServe()
}

技术栈选择:

  • Web框架:Gin、Echo
  • 数据库:GORM、sqlx
  • 缓存:go-redis
  • 配置管理:Viper
  • 日志:logrus、zap
  • 服务发现:Consul、etcd

微服务架构问题与挑战

3. 微服务架构相比单体架构有哪些主要问题?

面试官考察点: 对微服务缺点的深入理解

参考答案:

主要问题包括:

  1. 系统复杂性增加:需要管理多个服务的部署、监控、日志
  2. 服务间通信:网络延迟、故障处理、序列化开销
  3. 数据一致性:分布式事务、最终一致性
  4. 运维复杂度:需要更强的DevOps能力

4. 在Go中如何处理微服务间的通信?有哪些方式?

面试官考察点: Go微服务通信技术栈

参考答案:

通信方式对比:

gRPC示例:

// 定义服务
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
}

// Go客户端实现
func (c *UserClient) GetUser(ctx context.Context, userID int64) (*User, error) {
conn, err := grpc.Dial("user-service:50051", grpc.WithInsecure())
if err != nil {
return nil, err
}
defer conn.Close()

client := pb.NewUserServiceClient(conn)
return client.GetUser(ctx, &pb.GetUserRequest{UserId: userID})
}

消息队列示例(使用NATS):

func PublishOrderEvent(nc *nats.Conn, order *Order) error {
data, _ := json.Marshal(order)
return nc.Publish("order.created", data)
}

func SubscribeOrderEvents(nc *nats.Conn) {
nc.Subscribe("order.created", func(msg *nats.Msg) {
var order Order
json.Unmarshal(msg.Data, &order)
// 处理订单事件
})
}

数据一致性

5. 在微服务架构中如何保证数据一致性?请详细说明几种方案。

面试官考察点: 分布式系统数据一致性处理能力

参考答案:

数据一致性解决方案:

1. Saga模式实现:

Saga模式Go实现:

type SagaOrchestrator struct {
steps []SagaStep
}

type SagaStep struct {
Execute func() error
Compensate func() error
}

func (s *SagaOrchestrator) Execute() error {
executed := []int{}

for i, step := range s.steps {
if err := step.Execute(); err != nil {
// 执行补偿操作
s.compensate(executed)
return err
}
executed = append(executed, i)
}
return nil
}

func (s *SagaOrchestrator) compensate(executed []int) {
// 逆序执行补偿
for i := len(executed) - 1; i >= 0; i-- {
s.steps[executed[i]].Compensate()
}
}

2. 事件溯源模式:

type Event struct {
ID string `json:"id"`
Type string `json:"type"`
Data []byte `json:"data"`
Version int `json:"version"`
Timestamp time.Time `json:"timestamp"`
}

type EventStore interface {
Append(streamID string, events []Event) error
Load(streamID string, fromVersion int) ([]Event, error)
}

// 账户聚合根
type Account struct {
ID string
Balance decimal.Decimal
Version int
}

func (a *Account) Credit(amount decimal.Decimal) []Event {
return []Event{{
Type: "AccountCredited",
Data: marshal(CreditedEvent{Amount: amount}),
}}
}

func (a *Account) Apply(event Event) {
switch event.Type {
case "AccountCredited":
var e CreditedEvent
unmarshal(event.Data, &e)
a.Balance = a.Balance.Add(e.Amount)
}
a.Version++
}

6. 请解释CAP定理,在微服务设计中如何选择?

面试官考察点: 分布式系统理论基础

参考答案:

在微服务中的选择策略:

  • 金融支付服务:选择CP,保证数据一致性
  • 内容推荐服务:选择AP,保证服务可用性
  • 用户认证服务:选择CP,保证安全性

故障恢复与错误处理

7. 请设计一个完整的微服务熔断器模式,并用Go实现。

面试官考察点: 微服务容错设计能力

参考答案:

熔断器状态转换:

Go实现:

type CircuitBreaker struct {
name string
maxRequests uint32
interval time.Duration
timeout time.Duration
readyToTrip func(counts Counts) bool
onStateChange func(name string, from State, to State)

mutex sync.Mutex
state State
counts Counts
expiry time.Time
}

type State int

const (
StateClosed State = iota
StateHalfOpen
StateOpen
)

type Counts struct {
Requests uint32
TotalSuccesses uint32
TotalFailures uint32
ConsecutiveSuccesses uint32
ConsecutiveFailures uint32
}

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
generation, err := cb.beforeRequest()
if err != nil {
return nil, err
}

defer func() {
e := recover()
if e != nil {
cb.afterRequest(generation, false)
panic(e)
}
}()

result, err := req()
cb.afterRequest(generation, err == nil)
return result, err
}

func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
cb.mutex.Lock()
defer cb.mutex.Unlock()

now := time.Now()
state, generation := cb.currentState(now)

if state == StateOpen {
return generation, ErrOpenState
} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
return generation, ErrTooManyRequests
}

cb.counts.onRequest()
return generation, nil
}

8. 在Go微服务中如何实现重试机制?

面试官考察点: 容错处理实现

参考答案:

重试策略时序图:

Go实现:

type RetryConfig struct {
MaxAttempts int
InitialDelay time.Duration
MaxDelay time.Duration
Multiplier float64
Jitter bool
}

func WithRetry(config RetryConfig, fn func() error) error {
var lastErr error
delay := config.InitialDelay

for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
if err := fn(); err == nil {
return nil
} else {
lastErr = err

if attempt == config.MaxAttempts {
break
}

// 计算下次重试延迟
if config.Jitter {
jitter := time.Duration(rand.Float64() * float64(delay) * 0.1)
delay += jitter
}

time.Sleep(delay)
delay = time.Duration(float64(delay) * config.Multiplier)
if delay > config.MaxDelay {
delay = config.MaxDelay
}
}
}

return fmt.Errorf("max attempts exceeded: %w", lastErr)
}

// 使用示例
func CallExternalService() error {
config := RetryConfig{
MaxAttempts: 3,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 2 * time.Second,
Multiplier: 2.0,
Jitter: true,
}

return WithRetry(config, func() error {
resp, err := http.Get("http://external-service/api")
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode >= 500 {
return fmt.Errorf("server error: %d", resp.StatusCode)
}
return nil
})
}

9. 在电商场景下,如何设计订单服务的错误处理和回退策略?

面试官考察点: 业务场景下的容错设计

参考答案:

订单处理流程:

错误处理和回退策略实现:

type OrderService struct {
inventoryClient InventoryClient
paymentClient PaymentClient
notifyClient NotificationClient
circuitBreaker *CircuitBreaker
}

type OrderResult struct {
OrderID string
Status string
Error error
}

func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*OrderResult, error) {
// 1. 创建订单记录(状态:待处理)
orderID, err := s.createOrderRecord(req)
if err != nil {
return nil, fmt.Errorf("create order record failed: %w", err)
}

// 2. 检查库存(带熔断器)
stockResult, err := s.circuitBreaker.Execute(func() (interface{}, error) {
return s.inventoryClient.CheckStock(ctx, req.ProductID, req.Quantity)
})

if err != nil {
// 库存服务不可用,降级处理
if errors.Is(err, ErrOpenState) {
return s.handleInventoryServiceDown(orderID, req)
}
return s.handleStockCheckFailed(orderID, err)
}

stock := stockResult.(*StockInfo)
if stock.Available < req.Quantity {
return s.handleInsufficientStock(orderID, stock.Available, req.Quantity)
}

// 3. 处理支付(带重试)
paymentErr := WithRetry(RetryConfig{
MaxAttempts: 3,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 1 * time.Second,
Multiplier: 2.0,
}, func() error {
return s.paymentClient.ProcessPayment(ctx, &PaymentRequest{
OrderID: orderID,
Amount: req.Amount,
UserID: req.UserID,
})
})

if paymentErr != nil {
return s.handlePaymentFailed(orderID, paymentErr)
}

// 4. 减库存
if err := s.inventoryClient.ReserveStock(ctx, req.ProductID, req.Quantity); err != nil {
// 减库存失败,需要退款
go s.compensatePayment(orderID, req.Amount)
return s.handleStockReserveFailed(orderID, err)
}

// 5. 发送通知(异步,失败不影响订单)
go s.sendNotificationAsync(orderID, req.UserID)

// 6. 更新订单状态为成功
s.updateOrderStatus(orderID, "SUCCESS")

return &OrderResult{
OrderID: orderID,
Status: "SUCCESS",
}, nil
}

// 错误处理函数
func (s *OrderService) handleInventoryServiceDown(orderID string, req *CreateOrderRequest) (*OrderResult, error) {
// 降级策略:允许超卖,后续异步处理
log.Warn("inventory service down, allowing oversell for order", orderID)

s.updateOrderStatus(orderID, "PENDING_STOCK_CHECK")

// 异步检查库存
go s.asyncStockCheck(orderID, req)

return &OrderResult{
OrderID: orderID,
Status: "PENDING_STOCK_CHECK",
}, nil
}

func (s *OrderService) handleInsufficientStock(orderID string, available, required int) (*OrderResult, error) {
s.updateOrderStatus(orderID, "FAILED_INSUFFICIENT_STOCK")

// 记录缺货事件,用于补货决策
s.recordStockoutEvent(orderID, available, required)

return &OrderResult{
OrderID: orderID,
Status: "FAILED_INSUFFICIENT_STOCK",
Error: fmt.Errorf("insufficient stock: available %d, required %d", available, required),
}, nil
}

func (s *OrderService) compensatePayment(orderID string, amount decimal.Decimal) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

err := WithRetry(RetryConfig{MaxAttempts: 5}, func() error {
return s.paymentClient.Refund(ctx, &RefundRequest{
OrderID: orderID,
Amount: amount,
})
})

if err != nil {
// 退款失败,记录到死信队列,人工处理
s.recordFailedRefund(orderID, amount, err)
}
}

服务划分

10. 如何基于领域驱动设计(DDD)来划分微服务?请举一个具体例子。

面试官考察点: 领域建模和服务划分能力

参考答案:

电商平台DDD划分:

具体服务划分实现:

// 用户域 - 聚合根
type User struct {
ID UserID
Email Email
Profile UserProfile
Status UserStatus
version int
}

// 用户域 - 值对象
type Email struct {
value string
}

func NewEmail(email string) (Email, error) {
if !isValidEmail(email) {
return Email{}, ErrInvalidEmail
}
return Email{value: email}, nil
}

// 用户域 - 领域服务
type UserService struct {
userRepo UserRepository
eventBus EventBus
}

func (s *UserService) RegisterUser(email string, password string) (*User, error) {
// 检查邮箱是否已存在
if exists, _ := s.userRepo.ExistsByEmail(email); exists {
return nil, ErrEmailAlreadyExists
}

// 创建用户
user := NewUser(email, password)

// 保存用户
if err := s.userRepo.Save(user); err != nil {
return nil, err
}

// 发布领域事件
s.eventBus.Publish(UserRegisteredEvent{
UserID: user.ID,
Email: user.Email,
Time: time.Now(),
})

return user, nil
}

// 订单域 - 聚合根
type Order struct {
ID OrderID
UserID UserID
Items []OrderItem
TotalAmount Money
Status OrderStatus
version int
}

// 订单域 - 领域服务
type OrderService struct {
orderRepo OrderRepository
productRepo ProductRepository
eventBus EventBus
}

func (s *OrderService) CreateOrder(userID UserID, items []CreateOrderItem) (*Order, error) {
// 验证商品信息
products, err := s.validateProducts(items)
if err != nil {
return nil, err
}

// 创建订单
order := NewOrder(userID, products)

// 保存订单
if err := s.orderRepo.Save(order); err != nil {
return nil, err
}

// 发布领域事件
s.eventBus.Publish(OrderCreatedEvent{
OrderID: order.ID,
UserID: order.UserID,
Items: order.Items,
Time: time.Now(),
})

return order, nil
}

11. 微服务拆分粒度如何把握?什么情况下应该拆分?什么情况下应该合并?

面试官考察点: 微服务粒度控制的实践经验

参考答案:

拆分决策模型:

拆分原则:

  1. 单一职责原则
// 好的拆分 - 职责单一
type UserService struct {
// 只关注用户管理
}

type OrderService struct {
// 只关注订单处理
}

// 不好的拆分 - 职责混乱
type UserOrderService struct {
// 混合了用户和订单逻辑
}
  1. 数据独立性
// 好的拆分 - 数据独立
type UserService struct {
userDB *sql.DB // 独立的用户数据库
}

type ProductService struct {
productDB *sql.DB // 独立的商品数据库
}
  1. 团队独立性
// 一个服务应该由一个团队维护
type PaymentService struct {
// 由支付团队维护
}

合并场景:

  • 服务间调用过于频繁
  • 数据强一致性要求
  • 团队规模不足
  • 部署运维成本过高

微服务治理

12. 请设计一个完整的微服务注册发现机制,包括健康检查。

面试官考察点: 服务治理核心组件设计

参考答案:

服务注册发现架构:

Go实现:

// 服务注册接口
type ServiceRegistry interface {
Register(service *ServiceInfo) error
Deregister(serviceID string) error
Discover(serviceName string) ([]*ServiceInfo, error)
Watch(serviceName string) (<-chan []*ServiceInfo, error)
}

type ServiceInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
Port int `json:"port"`
Tags []string `json:"tags"`
Meta map[string]string `json:"meta"`
Health HealthCheck `json:"health"`
}

type HealthCheck struct {
Type string `json:"type"` // http, tcp, grpc
URL string `json:"url"` // for http
Interval time.Duration `json:"interval"`
Timeout time.Duration `json:"timeout"`
TTL time.Duration `json:"ttl"`
}

// Consul实现
type ConsulRegistry struct {
client *consul.Client
config *ConsulConfig
}

func (r *ConsulRegistry) Register(service *ServiceInfo) error {
registration := &consul.AgentServiceRegistration{
ID: service.ID,
Name: service.Name,
Address: service.Address,
Port: service.Port,
Tags: service.Tags,
Meta: service.Meta,
Check: &consul.AgentServiceCheck{
HTTP: service.Health.URL,
Interval: service.Health.Interval.String(),
Timeout: service.Health.Timeout.String(),
TTL: service.Health.TTL.String(),
},
}

return r.client.Agent().ServiceRegister(registration)
}

func (r *ConsulRegistry) Discover(serviceName string) ([]*ServiceInfo, error) {
services, _, err := r.client.Health().Service(serviceName, "", true, nil)
if err != nil {
return nil, err
}

var result []*ServiceInfo
for _, service := range services {
result = append(result, &ServiceInfo{
ID: service.Service.ID,
Name: service.Service.Service,
Address: service.Service.Address,
Port: service.Service.Port,
Tags: service.Service.Tags,
Meta: service.Service.Meta,
})
}

return result, nil
}

// 服务启动时注册
func (s *Service) Start() error {
// 注册服务
serviceInfo := &ServiceInfo{
ID: fmt.Sprintf("%s-%s", s.name, uuid.New().String()),
Name: s.name,
Address: s.config.Host,
Port: s.config.Port,
Health: HealthCheck{
Type: "http",
URL: fmt.Sprintf("http://%s:%d/health", s.config.Host, s.config.Port),
Interval: 10 * time.Second,
Timeout: 3 * time.Second,
},
}

if err := s.registry.Register(serviceInfo); err != nil {
return fmt.Errorf("register service failed: %w", err)
}

// 优雅关闭时注销服务
go s.handleShutdown(serviceInfo.ID)

return s.server.ListenAndServe()
}

// 健康检查端点
func (s *Service) healthHandler(c *gin.Context) {
health := map[string]interface{}{
"status": "UP",
"timestamp": time.Now(),
"checks": map[string]interface{}{
"database": s.checkDatabase(),
"redis": s.checkRedis(),
},
}

c.JSON(http.StatusOK, health)
}

13. 如何设计API网关?需要包含哪些功能?

面试官考察点: 微服务网关设计能力

参考答案:

API网关功能架构:

Go网关实现:

type Gateway struct {
router *gin.Engine
discovery ServiceRegistry
loadBalancer LoadBalancer
authService AuthService
rateLimiter RateLimiter
circuitBreaker map[string]*CircuitBreaker
cache Cache
logger Logger
}

// 中间件栈
func (g *Gateway) setupMiddlewares() {
g.router.Use(
g.corsMiddleware(),
g.loggingMiddleware(),
g.rateLimitMiddleware(),
g.authMiddleware(),
g.circuitBreakerMiddleware(),
)
}

// 认证中间件
func (g *Gateway) authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "missing token"})
c.Abort()
return
}

user, err := g.authService.ValidateToken(token)
if err != nil {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
return
}

c.Set("user", user)
c.Next()
}
}

// 限流中间件
func (g *Gateway) rateLimitMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
userID = c.ClientIP()
}

allowed, err := g.rateLimiter.Allow(userID)
if err != nil || !allowed {
c.JSON(429, gin.H{"error": "rate limit exceeded"})
c.Abort()
return
}

c.Next()
}
}

// 熔断中间件
func (g *Gateway) circuitBreakerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
serviceName := c.GetString("service_name")

cb, exists := g.circuitBreaker[serviceName]
if !exists {
c.Next()
return
}

result, err := cb.Execute(func() (interface{}, error) {
c.Next()
if c.Writer.Status() >= 500 {
return nil, fmt.Errorf("server error: %d", c.Writer.Status())
}
return nil, nil
})

if err != nil {
c.JSON(503, gin.H{"error": "service unavailable"})
c.Abort()
}
}
}

// 动态路由
func (g *Gateway) setupRoutes() {
// 用户服务路由
userGroup := g.router.Group("/api/v1/users")
{
userGroup.GET("/:id", g.proxyToService("user-service"))
userGroup.POST("/", g.proxyToService("user-service"))
userGroup.PUT("/:id", g.proxyToService("user-service"))
}

// 订单服务路由
orderGroup := g.router.Group("/api/v1/orders")
{
orderGroup.GET("/:id", g.proxyToService("order-service"))
orderGroup.POST("/", g.proxyToService("order-service"))
}
}

// 服务代理
func (g *Gateway) proxyToService(serviceName string) gin.HandlerFunc {
return func(c *gin.Context) {
// 服务发现
services, err := g.discovery.Discover(serviceName)
if err != nil || len(services) == 0 {
c.JSON(503, gin.H{"error": "service unavailable"})
return
}

// 负载均衡
service := g.loadBalancer.Select(services)
targetURL := fmt.Sprintf("http://%s:%d%s",
service.Address, service.Port, c.Request.URL.Path)

// 转发请求
g.forwardRequest(c, targetURL)
}
}

func (g *Gateway) forwardRequest(c *gin.Context, targetURL string) {
// 构建请求
req, err := http.NewRequest(c.Request.Method, targetURL, c.Request.Body)
if err != nil {
c.JSON(500, gin.H{"error": "internal server error"})
return
}

// 复制headers
for key, values := range c.Request.Header {
for _, value := range values {
req.Header.Add(key, value)
}
}

// 添加追踪headers
req.Header.Set("X-Request-ID", uuid.New().String())
req.Header.Set("X-Forwarded-For", c.ClientIP())

// 发送请求
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
c.JSON(502, gin.H{"error": "bad gateway"})
return
}
defer resp.Body.Close()

// 复制响应
for key, values := range resp.Header {
for _, value := range values {
c.Header(key, value)
}
}

c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
}

14. 如何实现分布式链路追踪?

面试官考察点: 分布式系统可观测性

参考答案:

链路追踪架构:

Go实现(使用OpenTelemetry):

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

// 初始化追踪
func initTracer(serviceName string) error {
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://localhost:14268/api/traces"),
))
if err != nil {
return err
}

tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
)),
)

otel.SetTracerProvider(tp)
return nil
}

// HTTP中间件
func TracingMiddleware() gin.HandlerFunc {
tracer := otel.Tracer("gin-server")

return func(c *gin.Context) {
ctx, span := tracer.Start(c.Request.Context(), c.Request.URL.Path)
defer span.End()

// 添加span属性
span.SetAttributes(
attribute.String("http.method", c.Request.Method),
attribute.String("http.url", c.Request.URL.String()),
attribute.String("http.user_agent", c.Request.UserAgent()),
)

// 将context传递给下一个处理器
c.Request = c.Request.WithContext(ctx)
c.Next()

// 添加响应信息
span.SetAttributes(
attribute.Int("http.status_code", c.Writer.Status()),
)
}
}

// 服务间调用追踪
type TracedHTTPClient struct {
client *http.Client
tracer trace.Tracer
}

func NewTracedHTTPClient() *TracedHTTPClient {
return &TracedHTTPClient{
client: &http.Client{Timeout: 30 * time.Second},
tracer: otel.Tracer("http-client"),
}
}

func (c *TracedHTTPClient) Get(ctx context.Context, url string) (*http.Response, error) {
ctx, span := c.tracer.Start(ctx, "http.get")
defer span.End()

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
span.RecordError(err)
return nil, err
}

// 注入追踪头
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))

span.SetAttributes(
attribute.String("http.method", "GET"),
attribute.String("http.url", url),
)

resp, err := c.client.Do(req)
if err != nil {
span.RecordError(err)
return nil, err
}

span.SetAttributes(
attribute.Int("http.status_code", resp.StatusCode),
)

return resp, nil
}

// 数据库操作追踪
func (s *UserService) GetUser(ctx context.Context, userID int64) (*User, error) {
ctx, span := s.tracer.Start(ctx, "db.get_user")
defer span.End()

span.SetAttributes(
attribute.String("db.system", "mysql"),
attribute.String("db.operation", "SELECT"),
attribute.Int64("user.id", userID),
)

query := "SELECT id, name, email FROM users WHERE id = ?"
row := s.db.QueryRowContext(ctx, query, userID)

var user User
err := row.Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
span.RecordError(err)
return nil, err
}

return &user, nil
}