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.
The layers are:
- 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. - API (
internal/api/
): This layer acts as a bridge. It defines interfaces that expose the application's functionality. The servers in thecmd
layer call these API methods, which in turn orchestrate calls to the domain layer. - 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. - Store / Data Access: A sub-layer within the Domain packages. It defines interfaces for data persistence (e.g.,
store
ininternal/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 insideinternal
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 aServer
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 theUser
struct, validation logic (ValidateForCreate
), data sanitization (Sanitize
), and thestore
interface for data persistence.store_postgres.go
: Provides the concrete PostgreSQL implementation of thestore
interface. Because the business logic inusers.go
only depends on the interface, we could swap this file withstore_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 includelogger
,postgres
(connection management), andapm
(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:
- In lower layers (like the
store
), wrap any external errors (e.g., from the database driver) with context. - In the API or handler layer, check the error type to determine the appropriate HTTP status code.
- The
errWrapper
incmd/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))
}
}
}