This repository is a bootstrap template for high-modular backend apps. Use it when you want:
- clear separation between infrastructure and business logic
- high testability in usecase and logic
- reusable flow building blocks with thin repository adapters
Reference guideline:
cmd/- app entrypoints and binary targets
- wire config, clients, usecases, and transport bindings
- current targets:
cmd/httpfor HTTP servingcmd/mqfor NSQ consumers
config/- app configuration structs and loading bootstrap
- initialized once and injected into startup wiring
pkg/- shared infrastructure utilities and abstractions
- current modules:
pkg/dbfor DB init + transaction interfacepkg/handlerfor generic HTTP/NSQ adapters
internal/usecase/- flow orchestration for business features
- compose calls to logic + repository through repo interfaces
internal/logic/- reusable static logic blocks (conversion, calculation)
- accept interfaces for external dependency access
internal/repository/- thin external-call wrappers (DB/API)
- minimal logic; delegate conversions/algorithms elsewhere
internal/model/- reusable domain structs shared across usecases
- Load config with
config.InitConfig() - Initialize logic and repository modules
- Build infra clients (
*sql.DB,*http.Client) - Construct usecases
- Bind usecases to generic handlers + routes
- Start server
- Load config
- Initialize logic and repository modules
- Build clients and usecases
- Register consumers with
handler.NewGenericConsumer - Start NSQ consumers and handle graceful shutdown
flowchart LR
cmdHttp[cmd/http]
cmdMq[cmd/mq]
configPkg[config]
handlerPkg[pkg/handler]
usecasePkg[internal/usecase]
logicPkg[internal/logic]
repoPkg[internal/repository]
modelPkg[internal/model]
cmdHttp --> usecasePkg
cmdMq --> usecasePkg
cmdHttp --> configPkg
cmdMq --> configPkg
cmdHttp --> handlerPkg
cmdMq --> handlerPkg
usecasePkg --> logicPkg
usecasePkg --> repoPkg
usecasePkg --> modelPkg
logicPkg --> modelPkg
repoPkg --> modelPkg
Keep usecase as orchestrator and keep hard-to-test calls behind an interface.
type createOrderUsecase struct {
repo iCreateOrderRepo
}
type iCreateOrderRepo interface {
db.ITransaction
GetPromotion(coupon string, totalPrice float64) (model.PromotionData, error)
InsertOrder(tx *sql.Tx, order model.OrderData) (int64, error)
InsertOrderItem(tx *sql.Tx, orderID int64, order model.OrderItem) error
}
func (uc *createOrderUsecase) HandleMessage(ctx context.Context, input model.PaymentSuccess) (output handler.NsqHandlerResult, err error) {
totalPrice, err := price.CalculateTotalPrice(input.CouponUsed, input.Items, uc.repo)
if err != nil {
return output, err
}
if totalPrice != input.PaymentAmount {
output.Finish = true
return output, ERR_PYM_MISMATCH
}
return output, nil
}Source: internal/usecase/post_payment/create_order.go
Use static reusable function with interface argument for external data fetch.
type ICalculateTotalPrice interface {
GetPromotion(coupon string, totalPrice float64) (model.PromotionData, error)
}
func CalculateTotalPrice(coupon string, items []model.CheckoutItem, itf ICalculateTotalPrice) (float64, error)Source: internal/logic/price/total_price.go
Repository should be function-based in internal/repository/* packages.
Usecase may wrap these functions through its repo interface for testability.
package transaction
import (
"database/sql"
"github.com/jekiapp/hi-mod-arch/internal/model"
)
func SelectCartByUserID(db *sql.DB, userID int64) (model.CartData, error) {
data := model.CartData{}
rows, err := db.Query("SELECT * from cart WHERE user_id=$1", userID)
if err != nil {
return data, err
}
_ = rows
return data, nil
}Source: internal/repository/transaction/cart.go
- HTTP generic adapter in
pkg/handler/handler.go - NSQ generic adapter in
pkg/handler/nsq.go
These adapters handle deserialization + validation, then call usecase contracts.
- Add or update domain structs in
internal/modelonly if they are shared. - Create a single-purpose usecase file in
internal/usecase/<entity>/. - Define usecase input/output model and main method.
- Define a repo interface inside the usecase file for all external calls.
- Implement repo struct methods with thin wrappers to
internal/repository/*. - Move reusable conversion/calculation blocks into
internal/logic/*as static functions. - Wire new usecase in
cmd/http/init.goorcmd/mq/consumers.go. - Attach transport binding:
- HTTP route in
cmd/http/routes.go, or - MQ consumer registration in
cmd/mq/consumers.go.
- HTTP route in
- Add/update gomock generation comments (
//go:generate mockgen ...) for testable interfaces. - Keep business rules in usecase/logic; keep repository code minimal.
- This guide documents existing structure and intended bootstrap usage.
- It does not perform code cleanup or refactor by itself.