vandor.
Getting Started

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.mod

Let us walk through each part.

cmd/ -- Application Entry Points

cmd/
└── app/
    └── main.go

The 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.go

Contracts 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. A Task entity knows how to validate itself, transition between states, and enforce business rules.
  • valueobject/ -- Value objects like Money, EmailAddress, or TaskStatus. 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 (like ErrTaskAlreadyCompleted) 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-fx fx.In structs for dependency injection.
  • service/ -- Domain services for logic that spans multiple entities or does not naturally belong to a single entity. For example, a PricingService that 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.go

These 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.yaml

Your 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: myapp

runner/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: 30s

runner/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: 1

This 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.lock

vpkg.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 points
  • internal/core/ -- All business logic (contracts, contexts, domain, application)
  • config/ -- Configuration files
  • database/ent/schema/ -- Database schemas

VPKG manages (do not edit directly):

  • internal/transport/ -- HTTP/API layer
  • internal/infrastructure/ -- Database, cache, storage adapters
  • vpkg.yaml and vpkg.lock

Auto-generated (do not edit):

  • internal/core/_gen/ -- Module aggregation
  • internal/core/contexts/*/module_gen.go -- Per-context module registration
  • database/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: