Usage Guide & Core Concepts

This guide provides a deep dive into the architecture and core principles of the GoApp project. Understanding these concepts is key to effectively working with and extending the application.

Architectural Overview

GoApp is structured around Clean Architecture principles. The fundamental rule is that dependencies can only point inwards. The core of the application, containing the business logic, knows nothing about the outer layers like databases, web frameworks, or UI.

Dependency flow between the layers

The layers are:

  1. CMD / Servers (cmd/): The outermost layer. This layer is responsible for handling I/O. It includes HTTP servers, gRPC servers, and message queue subscribers. It knows about the API layer but not the business logic directly.
  2. API (internal/api/): This layer acts as a bridge. It defines interfaces that expose the application's functionality. The servers in the cmd layer call these API methods, which in turn orchestrate calls to the domain layer.
  3. Domain / Business Logic (internal/users/, internal/usernotes/): The heart of the application. This layer contains the core business logic, validation rules, and data structures (entities). It is completely independent of any external framework or database.
  4. Store / Data Access: A sub-layer within the Domain packages. It defines interfaces for data persistence (e.g., store in internal/users/users.go). The concrete implementations of these interfaces (e.g., store_postgres.go) handle the actual database operations. This use of interfaces is a key example of Dependency Inversion.

Directory Structure Breakdown

The project's directory structure is organized to reflect this architecture.

├── cmd/                # Entrypoints: servers, subscribers, CLIs
├── internal/           # All private application code
│   ├── api/            # API layer defining server interfaces
│   ├── configs/        # Configuration management
│   ├── pkg/            # Shared utility packages (logger, db, etc.)
│   └── users/          # A domain package (business logic for users)
│       ├── users.go    # Business logic, validation, store interface
│       └── store_postgres.go # PostgreSQL implementation of the store interface
├── lib/                # Public libraries/SDKs for external use
├── schemas/            # Database schema files
├── docker/             # Dockerfile and docker-compose.yml
└── main.go             # Main application entrypoint

Key Directories Explained

  • internal/: A special directory in Go. Code inside internal can only be imported by code within the same parent directory. This enforces the privacy of our application logic.

  • internal/api/: This package standardizes the application's exposed functionality. By defining a Server interface, we ensure that both an HTTP server and a gRPC server would use the exact same underlying logic, preventing inconsistencies. It acts as a facade over the domain services.

  • internal/users/ (Domain Package): This is where the business logic for the 'User' domain resides.

    • users.go: Defines the User struct, validation logic (ValidateForCreate), data sanitization (Sanitize), and the store interface for data persistence.
    • store_postgres.go: Provides the concrete PostgreSQL implementation of the store interface. Because the business logic in users.go only depends on the interface, we could swap this file with store_mongodb.go without changing any business logic.
  • internal/pkg/: This directory holds shared, generic packages that can be used across multiple parts of the application. These are utility packages, not business logic. Examples include logger, postgres (connection management), and apm (observability).

  • cmd/: Contains the main entry points for the application. The project is structured to support multiple binaries or services. For example:

    • cmd/server/http/: The HTTP web server.
    • cmd/server/grpc/: A placeholder for a future gRPC server.
    • cmd/subscribers/kafka/: A placeholder for a future Kafka consumer.

Error Handling

The project uses a custom error handling package, github.com/naughtygopher/errors, which is a drop-in replacement for Go's built-in errors package. This package provides several benefits:

  • Error Types: Differentiates between error types like Validation, NotFoundErr, InternalErr, etc.
  • Stack Traces: Automatically captures stack traces at the point of error creation, making debugging easier.
  • User-Friendly Messages: Allows attaching a user-friendly message to an error, which can be safely shown in an API response, while the full error details are logged internally.

The general strategy is:

  1. In lower layers (like the store), wrap any external errors (e.g., from the database driver) with context.
  2. In the API or handler layer, check the error type to determine the appropriate HTTP status code.
  3. The errWrapper in cmd/server/http/handlers.go centralizes this logic, logging 5xx errors with full stack traces and sending a clean JSON response to the client.
// cmd/server/http/handlers.go
func errWrapper(h func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        err := h(w, r)
        if err == nil {
            return
        }

        status, msg, _ := errors.HTTPStatusCodeMessage(err)
        webgo.SendError(w, msg, status)
        if status > 499 {
            logger.Error(r.Context(), errors.Stacktrace(err))
        }
    }
}