You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

构建带数据库的RESTful后端API及Go项目结构技术问询

Answers to Your Remote Facade & Go API Architecture Questions

Great questions—let’s break them down clearly, since you’re tying the Remote Facade pattern to a real-world database-backed REST API and Go project structure.


1. Remote Facade Granularity in Database Scenarios

First, let’s anchor back to the core of the Remote Facade pattern: it exists to minimize the overhead of remote calls (in your case, between clients and your REST API).

  • Splitting into per-property calls is not reasonable: If you exposed separate endpoints for street, city, and zip of an address, clients would need 3 separate HTTP requests to get full address data. This wastes bandwidth, increases latency, and adds unnecessary complexity for both clients and your API.
  • A coarse-grained "get data" method is far more sensible: Exposing a single endpoint (like GET /users/{id}/address) that fetches all address fields in one database query aligns perfectly with Remote Facade’s goals. Here’s why:
    • It reduces remote call count from 3 to 1 (client → API).
    • It reduces database roundtrips from 3 to 1 (API → DB), which is a big performance win.
    • The facade (your API endpoint) still acts as a wrapper: internally, it can map the full database result to your domain object, but it doesn’t add business logic. If you removed the facade (e.g., had clients directly call your domain layer’s data fetch methods, hypothetically), the core logic would still work.

The key takeaway: Remote Facade is about optimizing remote interactions, not breaking down local operations. Keep external APIs coarse-grained, even if internal operations are fine-grained.


2. Go Project Architecture & Remote Facade Role

Let’s map your proposed structure to the pattern and best practices for Go:

Are Controllers the Remote Facade?

Yes! Your REST API controllers fit exactly the Remote Facade definition:

  • They’re the entry point for remote (HTTP) calls from clients.
  • Their job is to translate HTTP requests into calls to your local domain logic, and translate domain responses back into HTTP responses.
  • They should not contain core business logic—only HTTP-specific logic like parameter validation, status code mapping, and request/response formatting. If you removed the controllers (e.g., switched to a CLI or gRPC interface instead of REST), your domain and database layers would still function independently.
Database Access: Domain vs. Helper Layer

Your domain layer should not directly call the database. Instead, use a database helper/repository layer to encapsulate all database-specific logic:

  • The domain layer focuses on business rules (e.g., "only return addresses for active users") and delegates data fetching/storage to the helper.
  • The database helper handles raw SQL queries, ORM operations, connection management, and error handling specific to your database (MySQL, PostgreSQL, etc.). This decouples your domain logic from database implementation details—if you ever switch databases, you only need to update the helper layer.
Is the Main-Controllers-Domain-Database Helper Structure Reasonable?

Absolutely! This is a clean, maintainable layered architecture that follows separation of concerns:

  • Main: Program entry point. Handles initialization (database connection, router setup, dependency injection) and starts the HTTP server. No business logic here.
  • Controllers: Remote Facade layer. Handles HTTP request/response lifecycle, validates inputs, routes requests to domain services.
  • Domain: Core business logic layer. Contains models, service functions, and all business rules. Depends on the database helper interface, not concrete implementations.
  • Database Helper: Data access layer. Encapsulates all database operations, exposes a clean interface to the domain layer.
Example Go Project Structure
your-api/
├── main.go               # Entry point: init DB, router, start server
├── controllers/          # Remote Facade: HTTP handlers
│   └── user_controller.go
├── domain/               # Business logic
│   ├── user.go           # Domain models
│   └── user_service.go   # Business service functions
└── db/                   # Database helper/repository
    ├── db.go             # DB connection setup
    └── user_repo.go      # User-specific DB operations
Quick Code Snippets to Illustrate

Controller (Remote Facade):

// controllers/user_controller.go
package controllers

import (
    "net/http"
    "strconv"
    "your-api/domain"

    "github.com/gin-gonic/gin"
)

type UserController struct {
    userService *domain.UserService
}

func NewUserController(us *domain.UserService) *UserController {
    return &UserController{userService: us}
}

func (uc *UserController) GetUserAddress(c *gin.Context) {
    // HTTP-specific logic only: validate params
    userIDStr := c.Param("id")
    userID, err := strconv.Atoi(userIDStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})
        return
    }

    // Delegate to domain layer (local fine-grained call)
    address, err := uc.userService.GetUserAddress(userID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    // HTTP-specific logic: format response
    c.JSON(http.StatusOK, address)
}

Domain Service:

// domain/user_service.go
package domain

import "your-api/db"

type UserService struct {
    userRepo *db.UserRepository
}

func NewUserService(ur *db.UserRepository) *UserService {
    return &UserService{userRepo: ur}
}

func (us *UserService) GetUserAddress(userID int) (*Address, error) {
    // Business logic: check if user exists
    user, err := us.userRepo.GetUserByID(userID)
    if err != nil {
        return nil, err
    }
    if user == nil {
        return nil, errors.New("user not found")
    }

    // Return domain model (no DB-specific logic here)
    return &Address{
        Street: user.Street,
        City:   user.City,
        Zip:    user.Zip,
    }, nil
}

Database Helper:

// db/user_repo.go
package db

import (
    "database/sql"
    "your-api/domain"
)

type UserRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (ur *UserRepository) GetUserByID(userID int) (*domain.User, error) {
    // DB-specific logic: raw query
    var user domain.User
    err := ur.db.QueryRow(
        "SELECT id, street, city, zip FROM users WHERE id = ?", 
        userID,
    ).Scan(&user.ID, &user.Street, &user.City, &user.Zip)
    
    if err == sql.ErrNoRows {
        return nil, nil
    }
    return &user, err
}

内容的提问来源于stack exchange,提问作者Lucas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:17:14