Skip to content

twodigitss/reserv-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReservGo

A REST API for restaurant reservations made in Go. Handles full booking workflow: client registration, table availability, payment processing, reservation creation, visit confirmation, and automatic refunds.

A Project to explore Clean Architecture principles, strict separation of concerns, dependency injection and Go patterns.

Live API

https://reservgo.onrender.com


File System

This codebase is organized with Clean Architecture notions and feature driven modular organization for modules. I left a description of each folder below along with some decision mini-notes.

  • cmd/api/: HTTP server entry point. Contains everything related to the API binary. Handlers are stored here and not in internal/ because those are specific adapters of this binary.
    • main.go: App entry point. Main file.
    • routes.go: Definition and route register.
    • container.go: Dependency Injection. Initializes repositories, services, usecases, handlers, and it wires them together.
    • handlers/: HTTP controllers. The only concern this directory follows is to receive http requests, call the business layer and return a http response. Does not contain business logic.
  • configs/: Loads environmental variables. All env variables goes through here. It's the only file that should do this.
  • infrastructure/: External dependencies implementations. They are outside internal/ because by definition contains swappable adapters (Supabase, Stripe, etc.). internal/ is made for inmutable business logic, Infrastructure is made for this reason.
    • core/: Utilities used in crosswide infrastructure, JWT and Rate limiting for example. They live in infrastructure/ and not in internal/ because depends on external libraries.
    • database/: Postgress based pgxpool connection and specific implementations for repositories (repos/).
    • payments/: Payment provider implementation. Currently it is a mock that implemments a interface that should satisfy the app needs
  • internal/: business logic / Domain rules layer. Whatever is inside this directory cannot be imported for external modules outside this project.
    • modules/: Independent domain modules (such as payment, reservation, staff, etc). Each module is atomic and contains structs (models.go), interfaces (repository.go) and their logic business (service.go). These modules cannot import other modules. (I mean 'atomic' in the sense of independency and self-sufficience. If it's deleted, no other module should have a problem with it).
    • shared/: Shared utilities: Standar structured responses, pagination, validators, etc.
    • usecases/: Flow orchestrators that involves two or more modules. If a endpoint requires one module, the handler can call it directly without going through a use case.

Responsabilities per layer

Layer What it knows What it does NOT know
cmd/api/handlers HTTP, gin, JSON business logic/rules
internal/usecases full flow, modules HTTP, DB
modules/service it's own domain rules Other modules and infrastructure
modules/repository Interface business logic/rules
infrastructure Stripe, Supabase, etc internal logic

Dependency rules

cmd/api/          =>  Can import everything (internal + infrastructure).
infrastructure/   =>  Can import 'internal and modules'
infrastructure/   =>  Can import other directories and modules of infrastructure 
infrastructure/   =>  Must not import cmd
internal/modules/ =>  Must not import infrastructure
internal/modules/ =>  Must not improt another modules
internal/modules/ =>  Must not import usecases
internal/usecases =>  Can import modules

Important questioning to define this limits: "If i delete a module, can only the usecases/handlers fail explicitly?" if anything else breaks, this limits are not well defined.


Design decisions

Some decisions i would like to remark.

Structs should be separated by layer

Do not share the same struct with all tags across layers.

  • Domain (models.go): No tags, pure type. Circulates throughout the app
  • DB (infrastructure/database/repos/): Unexported struct with db:"" tags. Specific to the database.
  • HTTP (handlers/): request/response structs (also unexported) with json:"" tags. Specific to the handler

And by that means, a module interface should only be declared with it's domain types. Example:

// Do. Use the struct defined in the module itself.
type Repository interface {
    ListAll(ctx context.Context) ([]Table, error)
}

// Do not. DBTables should only exist in `database/repos`
type Repository interface {
    ListAll(ctx context.Context) ([]DBTables, error)
}

Constructor's object must be unexportable

// infrastructure/database/repos/tables.go
type repoImpl struct { ... }  // nothing outside this module can modify the declaration.

// Do. Expose the interface and not the implementation obj. (plus: it's easier to wire too).
func NewTableRepo(db *pgxpool.Pool) tables.Repository

// Do not. Exposes the implementation obj and breaks encapsulation.
func NewTableRepo(db *pgxpool.Pool) *repoImpl

Dependency injections

container.go is the only authorized layer to wire all the modules, repositories and external implementations. This way also makes all dependency errors shows in compilation time and it's easier to test with mocks (i believe).

func BuildContainer(pool *pgxpool.Pool) *Container {
    // infrastructure recives the pool
    tableRepo := repos.NewTableRepo(pool) // injects the db implementation and returns interface tables.Repository
    tableService := tables.NewService(tableRepo) // receives that interface
    return &Container{
        Tables: handlers.NewTableHandler(tableService), // uses the service injected with the pool conn
    }
}

implementation flow to add a feature/endpoint

(and this somehow also explains a little bit how the file system structure is related to the layers in sequential order)

  1. internal/modules/{modulo}/models.go: Define domain entities (no tags structs. pure types).
  2. internal/modules/{modulo}/repository.go: Define the interfaces for the functions implementation.
  3. internal/modules/{modulo}/service.go: Define business logic using the interface declared previously.
  4. infrastructure/database/repos/{modulo}.go: Define / implemment the interface with real SQL. Use unexported specific structs for gathering db info. The constructor defines the interface, not the type. Alredy discussed this above.
  5. internal/usecases/: (Optional) If a feature/endpoint requires more than two modules, implemment a usecase for it. otherwise omit this.
  6. cmd/api/handlers/{modulo}.go: Define the HTTP requests and responses, call the usecase/module and return the data.
  7. cmd/api/container.go y routes.go: Inject dependencies and make a route/group for it.

API Endpoints

All endpoints except for staff login requires a valid JWT token in the Authorization header (Bearer <token>). Just login as a staff member and add header Authorization with value Bearer token to accede the other endpoints.

Method Endpoint Description
General
GET / Root endpoint showing all registered routes
Staff
POST /v1/staff/login Staff login to obtain a JWT token
Tables
GET /v1/tables/ List all tables
GET /v1/tables/:id Find a table by ID (includes availability)
Users
GET /v1/users/ List all users
POST /v1/users/ Create a new user
GET /v1/users/:uuid Find a user by UUID
DELETE /v1/users/:uuid Delete a user by UUID
Reservations
POST /v1/reservations/ Create a new reservation
GET /v1/reservations/ Get reservations by client (uses query param ?client_id=:uuid)
GET /v1/reservations/:uuid Get reservation by UUID
PATCH /v1/reservations/:uuid Update reservation details
POST /v1/reservations/:uuid/cancel Cancel a reservation
POST /v1/reservations/:uuid/refund Process a refund for a reservation

About

Restaurant reservation API built with Go/Gin and PostgreSQL.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages