Project Structure
Understand how every directory and file in a Vandor project is organized
Overview
Every Vandor project follows a consistent structure built around DDD principles. The structure separates your business logic (which you own and edit) from infrastructure code (which VPKG manages). Understanding this layout is key to working effectively with Vandor.
Here is the full picture:
myapp/
├── cmd/
│ └── app/
│ └── main.go
├── internal/
│ ├── core/
│ │ ├── contracts/
│ │ │ ├── command.go
│ │ │ ├── tx_contract.go
│ │ │ └── event_contract.go
│ │ ├── contexts/
│ │ │ └── <context>/
│ │ │ ├── domain/
│ │ │ │ ├── entity/
│ │ │ │ ├── valueobject/
│ │ │ │ ├── repository.go
│ │ │ │ ├── read_repository.go
│ │ │ │ └── errors.go
│ │ │ ├── application/
│ │ │ │ ├── usecase/
│ │ │ │ └── service/
│ │ │ ├── module.go
│ │ │ └── module_gen.go
│ │ └── _gen/
│ │ ├── contexts_gen.go
│ │ └── modules_gen.go
│ ├── transport/ (vpkg-managed)
│ ├── infrastructure/ (vpkg-managed)
│ ├── bootstrap/
│ │ ├── fx/
│ │ └── runtime/
│ └── config/
├── config/
│ ├── base.yaml
│ └── runner/
│ ├── app.yaml
│ └── worker.yaml
├── database/
│ ├── ent/
│ │ └── schema/
│ └── migrations/
├── vpkg.yaml
├── vpkg.lock
└── go.modLet us walk through each part.
cmd/ -- Application Entry Points
cmd/
└── app/
└── main.goThe cmd/app/main.go file is your application entry point. It bootstraps the uber-fx dependency injection container and starts the runtime. In most projects, you will not need to edit this file often -- Vandor's sync commands keep it updated as you add contexts and packages.
If your project has multiple entry points (for example, a separate CLI tool), you can add more directories under cmd/.
internal/core/ -- Your Business Logic
This is the heart of your project. Everything under internal/core/ is code you own and edit directly. It contains your domain models, business rules, use cases, and the contracts that define how your core communicates with the outside world.
contracts/
internal/core/contracts/
├── command.go
├── tx_contract.go
└── event_contract.goContracts define shared interfaces used across all contexts. These are the fundamental building blocks:
- command.go -- Base command interface for the command pattern
- tx_contract.go -- Transaction interface so use cases can work within database transactions without knowing the implementation
- event_contract.go -- Domain event interface for publishing and subscribing to events across contexts
These contracts ensure your contexts can interact without creating tight coupling between them.
contexts/
internal/core/contexts/
├── catalog/
│ ├── domain/
│ │ ├── entity/
│ │ ├── valueobject/
│ │ ├── repository.go
│ │ ├── read_repository.go
│ │ └── errors.go
│ ├── application/
│ │ ├── usecase/
│ │ └── service/
│ ├── module.go
│ └── module_gen.go
└── identity/
└── ...Each context is a bounded context in DDD terms -- a self-contained area of your business. The internal structure has two layers:
domain/ -- The innermost layer. This is pure business logic with zero external dependencies.
entity/-- Domain entities with their behavior. ATaskentity knows how to validate itself, transition between states, and enforce business rules.valueobject/-- Value objects likeMoney,EmailAddress, orTaskStatus. These are immutable types that are defined by their values rather than an identity.repository.go-- Repository interface for write operations (Create, Update, Delete). This is an interface, not an implementation -- the actual database code lives in the infrastructure layer.read_repository.go-- Repository interface for read operations (Find, List, Query). Separating reads from writes supports CQRS patterns if you need them later.errors.go-- Domain-specific error types. These errors carry business meaning (likeErrTaskAlreadyCompleted) rather than technical details.
application/ -- The orchestration layer. Use cases and services live here.
usecase/-- Each use case represents a single action:create_task,complete_task,assign_task. Use cases coordinate domain entities and call repository interfaces. They use uber-fxfx.Instructs for dependency injection.service/-- Domain services for logic that spans multiple entities or does not naturally belong to a single entity. For example, aPricingServicethat calculates prices based on multiple factors.
module.go -- The uber-fx module definition for this context. It provides all the use cases and services from this context to the DI container.
module_gen.go -- Auto-generated file that registers all use cases and services. Updated by vandor sync context <name>.
The key rule: domain code never imports infrastructure code. Your entities and repository interfaces live in domain/, and concrete database implementations live in internal/infrastructure/. Uber-fx connects them at startup.
_gen/
internal/core/_gen/
├── contexts_gen.go
└── modules_gen.goThese files are auto-generated by Vandor and updated whenever you run vandor sync core or vandor sync all. They aggregate all your context modules into a single uber-fx module that the bootstrap layer uses to wire the application.
Do not edit these files manually. They are regenerated every time you sync.
internal/transport/ -- HTTP and API Layer (VPKG-Managed)
internal/transport/ (vpkg-managed)This directory is created and managed by VPKG packages like @official/http-humachi. It contains:
- HTTP server configuration and setup
- Router registration
- Middleware chains
- Handler wiring
- API documentation generation
When you install @official/http-humachi, the transport layer is set up automatically. You add handlers through vandor vpkg exec commands, and the package takes care of registering them with the router.
Do not edit files in internal/transport/ directly. This directory is managed by VPKG packages. Use vandor vpkg exec commands to add handlers, middleware, and other transport components. Manual edits may be overwritten during package updates.
internal/infrastructure/ -- Database and External Services (VPKG-Managed)
internal/infrastructure/ (vpkg-managed)Similar to the transport layer, this directory is managed by VPKG packages like @official/entgo, @official/redis-cache, and @official/storage-s3. It contains:
- Database client setup and connection management
- Repository implementations (the concrete code that satisfies your domain interfaces)
- Cache client configuration
- Object storage clients
- Queue and messaging setup
Each VPKG package adds its infrastructure code here and registers it with uber-fx automatically.
Like internal/transport/, do not edit files in internal/infrastructure/ manually. These are VPKG-managed.
internal/bootstrap/ -- Application Startup
internal/bootstrap/
├── fx/
└── runtime/The bootstrap layer ties everything together at application startup.
fx/ -- Contains the uber-fx application setup. This is where all the modules (core contexts, transport, infrastructure) are composed into a single DI container. When you run vandor sync all, the generated code in _gen/ is used here to register all your context modules.
runtime/ -- Contains the runtime configuration for different execution modes. The app runtime starts the HTTP server. The worker runtime starts background job processing. Each runtime can have its own configuration in config/runner/.
internal/config/ -- Configuration Loading
internal/config/This directory contains the Go code that loads and parses your YAML configuration files. It uses struct tags to map YAML keys to Go struct fields, with support for environment variable overrides.
config/ -- Configuration Files
config/
├── base.yaml
└── runner/
├── app.yaml
└── worker.yamlYour application configuration is split into layers:
base.yaml -- Shared configuration that applies to all runners. This is where you put database connection strings, logging levels, application name, and any settings that are common across all execution modes.
app:
name: myapp
env: development
log:
level: info
format: json
database:
host: localhost
port: 5432
user: postgres
password: password
dbname: myapprunner/app.yaml -- Configuration specific to the app runner (the HTTP server). This is where you put server-specific settings like port, host, read timeouts, and CORS configuration.
server:
host: 0.0.0.0
port: 8080
read_timeout: 30s
write_timeout: 30srunner/worker.yaml -- Configuration specific to the worker runner (background job processing). This is where you put worker-specific settings like concurrency, queue names, and retry policies.
worker:
concurrency: 10
queues:
default: 6
critical: 10
low: 1This layered approach means you can share database configuration between your HTTP server and your worker process while giving each one its own specific settings.
database/ -- Schemas and Migrations
database/
├── ent/
│ └── schema/
└── migrations/This directory exists when you have @official/entgo and/or @official/atlas installed.
ent/schema/ -- Ent.go schema definitions. Each file defines a database table using Ent's schema DSL. For example:
// database/ent/schema/task.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
type Task struct {
ent.Schema
}
func (Task) Fields() []ent.Field {
return []ent.Field{
field.String("id").Unique(),
field.String("title"),
field.Text("description").Optional(),
field.String("status").Default("pending"),
field.Time("created_at"),
field.Time("updated_at"),
}
}migrations/ -- Atlas migration files. These are auto-generated SQL migration files that track schema changes over time. You generate new migrations when your Ent schemas change.
vpkg.yaml and vpkg.lock
vpkg.yaml
vpkg.lockvpkg.yaml -- Declares which VPKG packages your project uses. This file is updated automatically when you run vandor vpkg add or vandor vpkg remove.
packages:
- name: "@official/http-humachi"
version: "0.4.0"
- name: "@official/entgo"
version: "0.4.0"
- name: "@official/atlas"
version: "0.4.0"vpkg.lock -- Lock file that pins exact versions and checksums for reproducible installs. Similar to go.sum or package-lock.json.
What You Own vs. What VPKG Manages
This is an important distinction:
You own and freely edit:
cmd/-- Entry pointsinternal/core/-- All business logic (contracts, contexts, domain, application)config/-- Configuration filesdatabase/ent/schema/-- Database schemas
VPKG manages (do not edit directly):
internal/transport/-- HTTP/API layerinternal/infrastructure/-- Database, cache, storage adaptersvpkg.yamlandvpkg.lock
Auto-generated (do not edit):
internal/core/_gen/-- Module aggregationinternal/core/contexts/*/module_gen.go-- Per-context module registrationdatabase/migrations/-- SQL migration files
This separation keeps your workflow clean. You focus on business logic in internal/core/, and VPKG handles the infrastructure plumbing.
How Code Flows
Here is how a request moves through the architecture:
HTTP POST /api/tasks
|
v
internal/transport/ HTTP handler receives request
|
v
internal/core/contexts/ Use case executes business logic
task_management/
application/usecase/
create_task.go
|
v
internal/core/contexts/ Domain entity validates and applies rules
task_management/
domain/entity/task.go
|
v
internal/core/contexts/ Repository interface (domain boundary)
task_management/
domain/repository.go
|
v
internal/infrastructure/ Concrete database implementation
|
v
PostgreSQL (or whatever database you chose)Dependencies always point inward: infrastructure depends on domain (through interfaces), never the other way around. This is enforced by Go's import rules and the directory structure.
Next Steps
Now that you understand the structure: