@nakedev/go-scaffold 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +567 -513
- package/dist/commands/auth.js +12 -1
- package/dist/commands/check.js +281 -0
- package/dist/commands/create.js +2 -1
- package/dist/commands/generate.js +5 -2
- package/dist/commands/method.js +66 -70
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +19 -8
- package/dist/commands/undo.js +6 -3
- package/dist/commands/worker.js +14 -4
- package/dist/index.js +13 -1
- package/dist/templates/auth-manifest.js +46 -45
- package/dist/templates/create-manifest.js +4 -0
- package/dist/templates/module-manifest.js +82 -60
- package/dist/templates/rbac-manifest.js +15 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +2 -0
- package/dist/utils/auth-patcher.js +22 -22
- package/dist/utils/config.js +24 -5
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +3 -3
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/platform-patcher.js +27 -0
- package/dist/utils/rbac-patcher.js +73 -216
- package/package.json +1 -1
- package/templates/add/auth/cmd/seed/main.go.hbs +2 -0
- package/templates/add/auth/internal/app/user/{browser_policy.go.hbs → adapters/inbound/http/browser_policy.go.hbs} +3 -3
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/{handler.go.hbs → adapters/inbound/http/handler.go.hbs} +103 -8
- package/templates/add/auth/internal/app/user/{handler_local.go.hbs → adapters/inbound/http/handler_local.go.hbs} +8 -7
- package/templates/add/auth/internal/app/user/{handler_mfa.go.hbs → adapters/inbound/http/handler_mfa.go.hbs} +6 -6
- package/templates/add/auth/internal/app/user/{handler_oauth.go.hbs → adapters/inbound/http/handler_oauth.go.hbs} +17 -17
- package/templates/add/auth/internal/app/user/{handler_recovery.go.hbs → adapters/inbound/http/handler_recovery.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{handler_test.go.hbs → adapters/inbound/http/handler_test.go.hbs} +52 -31
- package/templates/add/auth/internal/app/user/{handler_user.go.hbs → adapters/inbound/http/handler_user.go.hbs} +4 -4
- package/templates/add/auth/internal/app/user/{session_cookie.go.hbs → adapters/inbound/http/session_cookie.go.hbs} +5 -3
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/{mfa_store.go.hbs → adapters/outbound/postgres/mfa_store.go.hbs} +22 -20
- package/templates/add/auth/internal/app/user/{mfa_store_test.go.hbs → adapters/outbound/postgres/mfa_store_test.go.hbs} +7 -7
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/{tokenstore_pg.go.hbs → adapters/outbound/postgres/tokenstore_pg.go.hbs} +36 -32
- package/templates/add/auth/internal/app/user/{tokenstore_pg_test.go.hbs → adapters/outbound/postgres/tokenstore_pg_test.go.hbs} +10 -3
- package/templates/add/auth/internal/app/user/{tokenstore_recovery.go.hbs → adapters/outbound/postgres/tokenstore_recovery.go.hbs} +29 -3
- package/templates/add/auth/internal/app/user/{tokenstore_redis.go.hbs → adapters/outbound/redis/tokenstore.go.hbs} +43 -36
- package/templates/add/auth/internal/app/user/{tokenstore_redis_test.go.hbs → adapters/outbound/redis/tokenstore_test.go.hbs} +14 -3
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/{external_login.go.hbs → application/external_login.go.hbs} +30 -40
- package/templates/add/auth/internal/app/user/{jwt.go.hbs → application/jwt.go.hbs} +6 -3
- package/templates/add/auth/internal/app/user/{local_auth.go.hbs → application/local_auth.go.hbs} +17 -19
- package/templates/add/auth/internal/app/user/{mfa_service.go.hbs → application/mfa_service.go.hbs} +27 -28
- package/templates/add/auth/internal/app/user/{mfa_service_test.go.hbs → application/mfa_service_test.go.hbs} +12 -11
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +3 -3
- package/templates/add/auth/internal/app/user/{provider_test.go.hbs → application/provider_test.go.hbs} +55 -56
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +16 -47
- package/templates/add/auth/internal/app/user/{recovery_service.go.hbs → application/recovery_service.go.hbs} +13 -15
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/{service_test.go.hbs → application/service_test.go.hbs} +94 -75
- package/templates/add/auth/internal/app/user/{sessions.go.hbs → application/sessions.go.hbs} +14 -14
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/{user_query.go.hbs → application/user_query.go.hbs} +16 -16
- package/templates/add/auth/internal/app/user/composition.go.hbs +89 -86
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_users.up.sql.hbs +2 -1
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +23 -10
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +54 -36
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +55 -31
- package/templates/create/base/README.md.hbs +77 -22
- package/templates/create/base/cmd/api/wiring.go.hbs +3 -2
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/features/docs/architecture.md.hbs +64 -26
- package/templates/create/features/docs/patterns.md.hbs +117 -90
- package/templates/create/features/docs/techstack.md.hbs +17 -2
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -537
- package/templates/add/auth/internal/app/user/contracts.go.hbs +0 -88
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -134
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -68
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -45
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -32
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/mfa_challenge.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/mfa_enrollment.go.hbs +0 -20
- package/templates/add/auth/internal/app/user/model/mfa_recovery_code.go.hbs +0 -17
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -139
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -135
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -58
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/commands.go.hbs +0 -95
- package/templates/generate/module/composition.go.hbs +0 -23
- package/templates/generate/module/cqrs_test.go.hbs +0 -7
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -179
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/commands.go.hbs +0 -34
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -82
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/queries.go.hbs +0 -45
- package/templates/generate/module/minimal/service.go.hbs +0 -71
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/queries.go.hbs +0 -62
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -164
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
--
|
|
2
|
-
--
|
|
3
|
-
|
|
4
|
-
DO $$
|
|
5
|
-
BEGIN
|
|
6
|
-
IF EXISTS (SELECT 1 FROM user_svc.users WHERE role NOT IN ('staff', 'admin')) THEN
|
|
7
|
-
RAISE EXCEPTION 'cannot roll back add_roles: users still have a role other than staff/admin — reassign them to staff/admin first';
|
|
8
|
-
END IF;
|
|
9
|
-
END $$;
|
|
10
|
-
|
|
11
|
-
ALTER TABLE user_svc.users DROP COLUMN role;
|
|
1
|
+
-- Auth owns users.role, so RBAC removes only the constraint it added. The
|
|
2
|
+
-- column and existing assignments remain valid for auth-only operation.
|
|
3
|
+
ALTER TABLE user_svc.users DROP CONSTRAINT IF EXISTS users_role_fkey;
|
|
12
4
|
|
|
13
5
|
DROP TABLE role_svc.role_permissions;
|
|
14
6
|
DROP TABLE role_svc.permissions;
|
|
@@ -33,9 +33,20 @@ INSERT INTO role_svc.permissions (code, description) VALUES
|
|
|
33
33
|
INSERT INTO role_svc.role_permissions (role_code, permission_code)
|
|
34
34
|
SELECT 'admin', code FROM role_svc.permissions;
|
|
35
35
|
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
-- Auth owns the stable users.role column. RBAC owns the role catalog and adds
|
|
37
|
+
-- only the cross-schema constraint after that catalog exists. This keeps each
|
|
38
|
+
-- migration aligned with its module instead of making RBAC recreate auth's
|
|
39
|
+
-- persistence shape.
|
|
40
|
+
DO $$
|
|
41
|
+
BEGIN
|
|
42
|
+
IF NOT EXISTS (
|
|
43
|
+
SELECT 1
|
|
44
|
+
FROM pg_constraint
|
|
45
|
+
WHERE conname = 'users_role_fkey'
|
|
46
|
+
AND conrelid = 'user_svc.users'::regclass
|
|
47
|
+
) THEN
|
|
48
|
+
ALTER TABLE user_svc.users
|
|
49
|
+
ADD CONSTRAINT users_role_fkey
|
|
50
|
+
FOREIGN KEY (role) REFERENCES role_svc.roles(code);
|
|
51
|
+
END IF;
|
|
52
|
+
END $$;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package queue
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"os"
|
|
7
|
+
"testing"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/google/uuid"
|
|
11
|
+
"gorm.io/driver/postgres"
|
|
12
|
+
"gorm.io/gorm"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type riverRoundTripJob struct {
|
|
16
|
+
Marker string `json:"marker"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func (riverRoundTripJob) Kind() string { return "queue-test-round-trip" }
|
|
20
|
+
|
|
21
|
+
// TestRiverEnqueueAndWorkerProcess proves the Postgres queue path beyond
|
|
22
|
+
// client construction: a job is written, picked up by the worker adapter,
|
|
23
|
+
// decoded, and handed to the registered backend-neutral handler.
|
|
24
|
+
func TestRiverEnqueueAndWorkerProcess(t *testing.T) {
|
|
25
|
+
dsn := os.Getenv("TEST_DB_DSN")
|
|
26
|
+
if dsn == "" {
|
|
27
|
+
if os.Getenv("REQUIRE_TEST_DB") == "true" {
|
|
28
|
+
t.Fatal("TEST_DB_DSN is required when REQUIRE_TEST_DB=true")
|
|
29
|
+
}
|
|
30
|
+
t.Skip("River integration test skipped: set TEST_DB_DSN to a database with River migrations")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
|
34
|
+
if err != nil {
|
|
35
|
+
t.Fatalf("open test database: %v", err)
|
|
36
|
+
}
|
|
37
|
+
sqlDB, err := db.DB()
|
|
38
|
+
if err != nil {
|
|
39
|
+
t.Fatalf("get test database handle: %v", err)
|
|
40
|
+
}
|
|
41
|
+
t.Cleanup(func() { _ = sqlDB.Close() })
|
|
42
|
+
|
|
43
|
+
worker, err := NewRiverWorker(db, 1)
|
|
44
|
+
if err != nil {
|
|
45
|
+
t.Fatalf("create River worker: %v", err)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
received := make(chan string, 1)
|
|
49
|
+
worker.Handle((riverRoundTripJob{}).Kind(), func(_ context.Context, raw []byte) error {
|
|
50
|
+
var job riverRoundTripJob
|
|
51
|
+
if err := json.Unmarshal(raw, &job); err != nil {
|
|
52
|
+
return err
|
|
53
|
+
}
|
|
54
|
+
received <- job.Marker
|
|
55
|
+
return nil
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
workerCtx, cancel := context.WithCancel(context.Background())
|
|
59
|
+
t.Cleanup(func() {
|
|
60
|
+
cancel()
|
|
61
|
+
stopCtx, stopCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
62
|
+
defer stopCancel()
|
|
63
|
+
if err := worker.Stop(stopCtx); err != nil {
|
|
64
|
+
t.Errorf("stop River worker: %v", err)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
if err := worker.Start(workerCtx); err != nil {
|
|
68
|
+
t.Fatalf("start River worker: %v", err)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
expected := uuid.NewString()
|
|
72
|
+
if err := worker.Enqueue(context.Background(), riverRoundTripJob{Marker: expected}, nil); err != nil {
|
|
73
|
+
t.Fatalf("enqueue River job: %v", err)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
select {
|
|
77
|
+
case actual := <-received:
|
|
78
|
+
if actual != expected {
|
|
79
|
+
t.Fatalf("worker received marker %q, want %q", actual, expected)
|
|
80
|
+
}
|
|
81
|
+
case <-time.After(10 * time.Second):
|
|
82
|
+
t.Fatal("River worker did not process the enqueued job within 10s")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -76,6 +76,7 @@ Run from the project root and pass every value as a flag:
|
|
|
76
76
|
go-scaffold generate module <name> [--profile <lean|crud|cqrs>] [--defaults] [--auth] [--permission <code>]
|
|
77
77
|
go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]
|
|
78
78
|
go-scaffold generate migration <name>
|
|
79
|
+
go-scaffold check
|
|
79
80
|
go-scaffold config
|
|
80
81
|
go-scaffold add auth --store <postgres|redis> --browser-topology <same-origin|same-site|cross-site> [--defaults] [--yes]
|
|
81
82
|
go-scaffold add worker --queue <postgres|redis> [--defaults]
|
|
@@ -116,10 +117,10 @@ corresponding features installed. There is no final confirmation for module
|
|
|
116
117
|
generation; review the profile and flags before running it.
|
|
117
118
|
|
|
118
119
|
`go-scaffold config` changes defaults for future modules only. Existing modules
|
|
119
|
-
are not rewritten. `config show
|
|
120
|
-
read-only and do not open a wizard. The bare `generate` command
|
|
121
|
-
whether to generate a module, method, or migration; the bare `add`
|
|
122
|
-
first asks which infrastructure feature to add.
|
|
120
|
+
are not rewritten. `check`, `config show`, and `config validate` are
|
|
121
|
+
intentionally read-only and do not open a wizard. The bare `generate` command
|
|
122
|
+
first asks whether to generate a module, method, or migration; the bare `add`
|
|
123
|
+
command first asks which infrastructure feature to add.
|
|
123
124
|
|
|
124
125
|
## Generated module contract
|
|
125
126
|
|
|
@@ -128,34 +129,41 @@ defaults. On a fresh project that is a Lean module with:
|
|
|
128
129
|
|
|
129
130
|
~~~text
|
|
130
131
|
internal/app/<pkg>/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
service.go
|
|
132
|
+
domain/entity.go
|
|
133
|
+
domain/errors.go
|
|
134
|
+
ports/repository.go
|
|
135
|
+
application/dto.go
|
|
136
|
+
application/service.go
|
|
137
|
+
application/service_test.go
|
|
138
|
+
adapters/inbound/http/dto.go
|
|
139
|
+
adapters/inbound/http/handler.go
|
|
140
|
+
adapters/inbound/http/handler_test.go
|
|
141
|
+
adapters/outbound/postgres/model.go
|
|
142
|
+
adapters/outbound/postgres/repository.go
|
|
143
|
+
adapters/outbound/postgres/repository_test.go
|
|
136
144
|
composition.go
|
|
137
|
-
handler.go
|
|
138
|
-
service_test.go
|
|
139
|
-
handler_test.go
|
|
140
|
-
repository_test.go
|
|
141
145
|
migrations/<version>_create_<plural>.{up,down}.sql
|
|
142
146
|
~~~
|
|
143
147
|
|
|
144
148
|
The Lean/minimal module has no endpoint yet, but includes the complete
|
|
145
149
|
data-access surface and test seams needed by generate method. The CRUD profile
|
|
146
150
|
adds a CRUD starter and routes list/get/create/update/delete. The CQRS profile
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
replaces `application/service.go` and its test with
|
|
152
|
+
`application/commands.go`, `application/queries.go`, and
|
|
153
|
+
`application/cqrs_test.go`; Advanced can combine CRUD + CQRS, or use CQRS
|
|
154
|
+
alone before adding methods one at a time. Service and CQRS are mutually
|
|
155
|
+
exclusive within one module, but may coexist across modules.
|
|
149
156
|
With OpenAPI enabled it also creates the per-domain documents and updates
|
|
150
157
|
docs/openapi.yaml.
|
|
151
158
|
|
|
152
159
|
Feature-local composition.go constructs repository → application handlers →
|
|
153
160
|
handler. A module generated with `--cqrs` constructs separate command/query
|
|
154
161
|
handlers there; a regular module constructs its service there.
|
|
155
|
-
|
|
156
|
-
infrastructure,
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
`internal/composition/` owns process-level adapters between feature ports.
|
|
163
|
+
`cmd/api/wiring.go` selects shared infrastructure, invokes those adapters, and
|
|
164
|
+
registers routes. Do not move business rules or feature-internal constructors
|
|
165
|
+
into either process-level location, or hand-edit root route markers for a new
|
|
166
|
+
endpoint.
|
|
159
167
|
|
|
160
168
|
## Method shapes and stub safety
|
|
161
169
|
|
|
@@ -182,14 +190,19 @@ This project is modular plus Hexagonal/DDD-friendly, with CQRS available via
|
|
|
182
190
|
the `cqrs` profile or the backwards-compatible
|
|
183
191
|
`go-scaffold generate module <name> --cqrs` flag:
|
|
184
192
|
|
|
185
|
-
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
193
|
+
- `adapters/inbound/http/dto.go` owns JSON names, binding/validation tags, and
|
|
194
|
+
request/response mapping; handlers are inbound adapters and know HTTP/Gin
|
|
195
|
+
only;
|
|
196
|
+
- `application/service.go` or `application/commands.go` plus
|
|
197
|
+
`application/queries.go` are application/use-case boundaries and depend on
|
|
198
|
+
narrow ports. The service and CQRS shapes are mutually exclusive inside one
|
|
199
|
+
module;
|
|
200
|
+
- `domain/` entities and invariants stay independent of Gin, GORM, Redis, and
|
|
201
|
+
HTTP; persistence models belong under `adapters/outbound/postgres`;
|
|
190
202
|
- repositories/token stores/mail/queue clients are outbound adapters;
|
|
191
|
-
- composition.go wires one feature locally,
|
|
192
|
-
the process
|
|
203
|
+
- composition.go wires one feature locally, internal/composition/ wires
|
|
204
|
+
cross-feature capabilities, and cmd/api/wiring.go boots the process and
|
|
205
|
+
registers routes.
|
|
193
206
|
|
|
194
207
|
DDD does not require every CRUD row to become a large aggregate. CQRS optional:
|
|
195
208
|
use the CQRS profile when command and query models, consistency, or
|
|
@@ -199,9 +212,10 @@ service and repository port are clearer. Do not add a broker, a second
|
|
|
199
212
|
database, or empty command/query layers just for naming.
|
|
200
213
|
|
|
201
214
|
Never import another feature's private model, repository, or handler. For
|
|
202
|
-
cross-feature behavior, define a narrow public application port,
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
cross-feature behavior, define a narrow public application port, put a
|
|
216
|
+
synchronous adapter in internal/composition/ (importing only the feature's
|
|
217
|
+
public package root), use a documented feature API, or publish an explicit
|
|
218
|
+
event. Keep dependencies flowing inward:
|
|
205
219
|
|
|
206
220
|
~~~text
|
|
207
221
|
handler -> application/use case -> domain + ports -> adapters
|
|
@@ -209,13 +223,13 @@ handler -> application/use case -> domain + ports -> adapters
|
|
|
209
223
|
+--- composition -+
|
|
210
224
|
~~~
|
|
211
225
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
Auth and RBAC use the same canonical split as every other feature:
|
|
227
|
+
`domain/`, `application/`, `ports/`, `adapters/`, and a feature-local
|
|
228
|
+
`composition.go`. Auth owns user use cases and ports; RBAC owns role policy and
|
|
229
|
+
authorization adapters. There is no `compat/`, feature-level `model/`, or
|
|
230
|
+
second implementation tree. If an existing security flow needs a refactor,
|
|
231
|
+
split it at the application boundary and keep provider SDKs, Gin, and GORM
|
|
232
|
+
inside their respective adapters.
|
|
219
233
|
|
|
220
234
|
## Authentication and browser OAuth contract
|
|
221
235
|
|
|
@@ -345,6 +359,10 @@ still requires PostgreSQL even when refresh tokens use Redis. When routes or
|
|
|
345
359
|
OpenAPI templates change, inspect the updated `docs/openapi.yaml` and run the
|
|
346
360
|
configured OpenAPI linter.
|
|
347
361
|
|
|
362
|
+
When the project uses River, run `make river-migrate-test` after
|
|
363
|
+
`make migrate-up-test`. The River integration test must enqueue a real job and
|
|
364
|
+
observe the worker handler; a process that only boots is not enough evidence.
|
|
365
|
+
|
|
348
366
|
Only in the `go-scaffold` source repository, run `pnpm run verify` for CLI or
|
|
349
367
|
template changes, then generate a fresh sample project and inspect both its
|
|
350
368
|
source and guidance files. A generated Go project does not contain the
|
|
@@ -15,8 +15,8 @@ linters:
|
|
|
15
15
|
depguard:
|
|
16
16
|
# What keeps this a modular monolith rather than a pile of folders: a
|
|
17
17
|
# domain may not reach into another domain's package. It declares a
|
|
18
|
-
# narrow interface for what it needs and
|
|
19
|
-
# concrete service — see docs/architect/patterns.md "Calling Another
|
|
18
|
+
# narrow interface for what it needs and internal/composition plus
|
|
19
|
+
# cmd/api/wiring.go supply the concrete service — see docs/architect/patterns.md "Calling Another
|
|
20
20
|
# Domain's Logic". Without this, the first shortcut someone takes stays
|
|
21
21
|
# invisible until the coupling is everywhere.
|
|
22
22
|
#
|
|
@@ -29,21 +29,23 @@ domain-oriented design, and an optional CQRS style. They are compatible:
|
|
|
29
29
|
apply them inside each feature instead of creating a second architecture or
|
|
30
30
|
an empty layer for every noun.
|
|
31
31
|
|
|
32
|
-
- **Inbound adapter:** `
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
32
|
+
- **Inbound adapter:** `adapters/inbound/http/dto.go` owns JSON/binding request
|
|
33
|
+
DTOs, response DTOs, and mapping at the HTTP boundary; `handler.go` parses
|
|
34
|
+
HTTP and calls the relevant application service or command/query port.
|
|
35
|
+
- **Application boundary:** `application/service.go` (or
|
|
36
|
+
`application/commands.go`/`application/queries.go` when
|
|
37
|
+
the module uses `--cqrs`) orchestrates use cases. It owns
|
|
37
38
|
workflow decisions and depends on narrow consumer-owned ports.
|
|
38
|
-
- **Domain:**
|
|
39
|
+
- **Domain:** entities, value rules, and invariants stay independent of Gin,
|
|
39
40
|
GORM, Redis, HTTP status codes, and environment variables.
|
|
40
|
-
- **Outbound adapter:** `repository.go`, token
|
|
41
|
+
- **Outbound adapter:** `adapters/outbound/postgres/repository.go`, token
|
|
42
|
+
stores, mail/queue clients,
|
|
41
43
|
and other platform integrations translate a port into persistence or I/O.
|
|
42
44
|
- **Composition:** the feature-local `composition.go` constructs its
|
|
43
|
-
repository,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
repository, application boundary, and handler. `internal/composition/` owns
|
|
46
|
+
process-level cross-feature adapters. `cmd/api/wiring.go` selects shared
|
|
47
|
+
infrastructure, supplies those adapters and security dependencies, and
|
|
48
|
+
registers feature routes; it does not contain cross-feature adapter logic.
|
|
47
49
|
|
|
48
50
|
The project defaults new modules to `{{defaultApplicationStyle}}` application
|
|
49
51
|
handling and a `{{defaultModuleSurface}}` surface. The `generate module` wizard
|
|
@@ -61,7 +63,9 @@ folder only to satisfy a label.
|
|
|
61
63
|
Keep feature boundaries real. Do not import another feature's private model,
|
|
62
64
|
repository, or handler. Expose a small public application port, call a
|
|
63
65
|
documented feature API, or use an explicit event when cross-feature behavior
|
|
64
|
-
is required.
|
|
66
|
+
is required. Put synchronous adapters in `internal/composition/`, import only
|
|
67
|
+
the other feature's public package root, and keep one-way dependencies. Keep
|
|
68
|
+
dependency direction flowing inward:
|
|
65
69
|
|
|
66
70
|
```text
|
|
67
71
|
handler -> application/use case -> domain + ports -> adapters
|
|
@@ -70,15 +74,28 @@ handler -> application/use case -> domain + ports -> adapters
|
|
|
70
74
|
```
|
|
71
75
|
|
|
72
76
|
The generated CRUD shape is an intentionally incremental starter. It gives
|
|
73
|
-
each module a testable
|
|
74
|
-
root; it does not decide the final aggregate, fields, invariants,
|
|
75
|
-
between commands and queries for the product.
|
|
77
|
+
each module a testable application/inbound seam and a feature-local
|
|
78
|
+
composition root; it does not decide the final aggregate, fields, invariants,
|
|
79
|
+
or split between commands and queries for the product.
|
|
80
|
+
|
|
81
|
+
For a CQRS module, `application/commands.go` and
|
|
82
|
+
`application/queries.go` are the primary application boundary and
|
|
83
|
+
`composition.go` passes separate command and query handlers to the inbound
|
|
84
|
+
adapter's `NewHandler`. A CQRS module must not also grow an
|
|
85
|
+
`application/service.go` facade. Service and CQRS may coexist across modules;
|
|
86
|
+
they are mutually exclusive inside one module.
|
|
87
|
+
|
|
88
|
+
Auth and RBAC are security-sensitive modules, but they use the same canonical
|
|
89
|
+
split boundary as every other feature. Auth owns user application use cases
|
|
90
|
+
and ports; RBAC owns role policy and authorization adapters. Their public
|
|
91
|
+
module roots compose those pieces, and one canonical implementation tree is
|
|
92
|
+
generated.
|
|
76
93
|
|
|
77
94
|
## Hard rule: use the CLI for generated surface
|
|
78
95
|
|
|
79
96
|
If you are creating a **new domain** (`internal/app/<name>/` with its own
|
|
80
|
-
|
|
81
|
-
domain, stop and run the CLI:
|
|
97
|
+
domain/application/ports/adapters boundary), or adding a **new endpoint** to
|
|
98
|
+
an existing domain, stop and run the CLI:
|
|
82
99
|
|
|
83
100
|
```bash
|
|
84
101
|
go-scaffold generate module <name> --defaults
|
|
@@ -107,8 +124,8 @@ Recognize indirect requests as generation work:
|
|
|
107
124
|
|
|
108
125
|
Run `go-scaffold generate module --help` or `generate method --help` when the
|
|
109
126
|
desired shape is unclear. Do not hand-edit generated route markers to add a
|
|
110
|
-
new endpoint; the CLI keeps
|
|
111
|
-
OpenAPI/migration patching in sync.
|
|
127
|
+
new endpoint; the CLI keeps the inbound adapter, application port, test seam,
|
|
128
|
+
and OpenAPI/migration patching in sync.
|
|
112
129
|
|
|
113
130
|
## What the CLI does not decide
|
|
114
131
|
|
|
@@ -166,13 +183,11 @@ approval before changing their contract.
|
|
|
166
183
|
- Password reset and email verification consume a one-time token in the same
|
|
167
184
|
retry-safe transaction as the user/identity update. A failed post-commit
|
|
168
185
|
session revocation must not make a successful reset impossible to retry.
|
|
169
|
-
- The generated auth
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
ports; a deeper separation of the existing auth facade is an explicit
|
|
175
|
-
architecture refactor and needs focused tests.
|
|
186
|
+
- The generated auth implementation is canonical: use cases live in
|
|
187
|
+
`application/`, domain rules in `domain/`, contracts in `ports/`, and
|
|
188
|
+
provider/HTTP/Postgres details in their adapters. Do not import Gin, provider
|
|
189
|
+
SDKs, or persistence models into new application use cases; keep the
|
|
190
|
+
token/OAuth/MFA tests as the security gate.
|
|
176
191
|
- Browser provider exchange consumes a one-time server-side transaction bound
|
|
177
192
|
to provider, state, S256 PKCE challenge, and OIDC nonce. Google ID tokens
|
|
178
193
|
must be signature, issuer, audience/azp, time-claim, nonce, and subject
|
|
@@ -231,6 +246,10 @@ correctness. MFA state still requires PostgreSQL even when refresh tokens use
|
|
|
231
246
|
Redis. When routes or OpenAPI templates change, inspect the updated
|
|
232
247
|
`docs/openapi.yaml` and run the configured OpenAPI linter.
|
|
233
248
|
|
|
249
|
+
When a River worker is installed, run `make river-migrate-test` in addition to
|
|
250
|
+
`make migrate-up-test`. The queue integration test must enqueue a real job and
|
|
251
|
+
observe the worker handler; a worker process that merely boots is not enough.
|
|
252
|
+
|
|
234
253
|
Only in the `go-scaffold` source repository, run the generator project's
|
|
235
254
|
`pnpm run verify` for CLI/template changes, then generate fresh sample projects
|
|
236
255
|
to inspect the output. A generated Go project does not contain the generator's
|
|
@@ -244,19 +263,24 @@ work untouched.
|
|
|
244
263
|
|
|
245
264
|
## Command quick reference
|
|
246
265
|
|
|
247
|
-
- `go-scaffold generate module <name> --defaults` — minimal
|
|
248
|
-
|
|
249
|
-
route registration, and versioned module
|
|
266
|
+
- `go-scaffold generate module <name> --defaults` — minimal domain entity,
|
|
267
|
+
errors, ports, application boundary, inbound/outbound adapters,
|
|
268
|
+
feature-local `composition.go`, root route registration, and versioned module
|
|
269
|
+
migrations. Add endpoints with
|
|
250
270
|
`generate method`, use `--profile crud` for a CRUD skeleton, or
|
|
251
271
|
`--profile cqrs` for separate command/query application handlers.
|
|
252
272
|
- `go-scaffold generate method <module> <name> --type <get|post|put|patch|delete> [--get-mode all|one] [--field <name>]` — patches an existing
|
|
253
|
-
module's handler
|
|
254
|
-
|
|
273
|
+
module's inbound handler, application service or command/query handler and,
|
|
274
|
+
for a GET-one lookup, its repository port/adapter plus test seam. It never
|
|
275
|
+
overwrites a same-named method.
|
|
255
276
|
- `go-scaffold generate migration <name>` — reserves a timestamped SQL
|
|
256
277
|
migration pair for a reviewed schema change.
|
|
257
278
|
- `go-scaffold config` — interactively change future module defaults;
|
|
258
279
|
`config show` prints the resolved manifest and `config validate` checks it
|
|
259
280
|
without writing.
|
|
281
|
+
- `go-scaffold check` — validates every module's split hexagonal layout,
|
|
282
|
+
forbidden layer dependencies, sibling imports, and the exclusive
|
|
283
|
+
service-versus-CQRS contract.
|
|
260
284
|
- `go-scaffold undo module <name> -y` — removes an unshipped generated module,
|
|
261
285
|
its migration, and its wiring. It refuses once those migrations are
|
|
262
286
|
committed or applied; use a reviewed drop migration for a shipped domain.
|
|
@@ -22,28 +22,55 @@ for scripts.
|
|
|
22
22
|
|
|
23
23
|
See `docs/architect/` for the conventions every generated module follows, and `AGENTS.md`/`CLAUDE.md` if you're working with an AI coding agent in this repo.
|
|
24
24
|
|
|
25
|
+
## Installed optional features
|
|
26
|
+
|
|
27
|
+
This project records its generated capabilities in `go-scaffold.config.json`:
|
|
28
|
+
|
|
29
|
+
- authentication: {{#if auth}}enabled{{else}}not installed — use `go-scaffold add auth` when needed{{/if}}
|
|
30
|
+
- background jobs: {{#if worker}}enabled{{else}}not installed — use `go-scaffold add worker` when needed{{/if}}
|
|
31
|
+
- RBAC: {{#if rbac}}enabled{{else}}not installed — use `go-scaffold add rbac` after auth when needed{{/if}}
|
|
32
|
+
- metrics and tracing: {{#if observability}}enabled{{else}}not installed — use `go-scaffold add observability` when needed{{/if}}
|
|
33
|
+
|
|
34
|
+
The `add` commands refresh untouched generated architecture docs and this
|
|
35
|
+
section. If a maintainer has edited a document, the command leaves it alone
|
|
36
|
+
and reports that it needs a manual update.
|
|
37
|
+
|
|
25
38
|
## Layout
|
|
26
39
|
|
|
27
40
|
```
|
|
28
41
|
cmd/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
├── api/
|
|
43
|
+
│ ├── main.go # process entry point, config, slog, graceful shutdown
|
|
44
|
+
│ └── wiring.go # process composition root and route registration
|
|
32
45
|
internal/
|
|
33
|
-
├──
|
|
34
|
-
|
|
46
|
+
├── composition/ # cross-feature adapters and process-level wiring
|
|
47
|
+
├── platform/ # adapters for external systems
|
|
48
|
+
│ └── database/ # opens the PostgreSQL connection + pool
|
|
35
49
|
├── shared/ # pure logic/framework glue, no I/O
|
|
36
50
|
│ ├── config/ # loads config from env
|
|
37
|
-
│ ├── apperror/
|
|
38
|
-
│ ├── dberr/ # maps DB errors to constraint kind
|
|
39
|
-
│ ├── httpx/ # HTTP helpers shared by every domain
|
|
40
|
-
│ ├── id/ # UUID v7 generation (id.New)
|
|
41
|
-
│ ├── middleware/ # RequestID, Logger
|
|
42
|
-
│ ├── pagination/
|
|
43
|
-
│ └── tx/ # carries a transaction on the
|
|
44
|
-
└── app/ #
|
|
51
|
+
│ ├── apperror/ # central error type (status + payload)
|
|
52
|
+
│ ├── dberr/ # maps DB errors to constraint kind — shared by every domain
|
|
53
|
+
│ ├── httpx/ # HTTP helpers shared by every domain
|
|
54
|
+
│ ├── id/ # UUID v7 generation (id.New)
|
|
55
|
+
│ ├── middleware/ # RequestID, Logger, Error, CORS{{#if auth}}, auth{{/if}}{{#if rbac}}, RBAC{{/if}}{{#if observability}}, metrics, tracing{{/if}}
|
|
56
|
+
│ ├── pagination/ # parses ?limit=&offset=, response envelope
|
|
57
|
+
│ └── tx/ # carries a transaction on the context
|
|
58
|
+
└── app/ # one package per domain, added with `generate module`
|
|
45
59
|
```
|
|
46
60
|
|
|
61
|
+
The tree shows the baseline. Installed optional features add the process and
|
|
62
|
+
platform packages listed in the section above without changing these module
|
|
63
|
+
boundaries.
|
|
64
|
+
|
|
65
|
+
Cross-feature adapters belong in `internal/composition/`, not in `cmd/api/`.
|
|
66
|
+
The API wiring file selects infrastructure, invokes those adapters, and
|
|
67
|
+
registers routes. Keep one-way dependencies and expose only narrow public
|
|
68
|
+
capabilities from each feature.
|
|
69
|
+
|
|
70
|
+
Optional features stay in the same architecture: auth owns user application
|
|
71
|
+
use cases and provider ports, RBAC owns role policy, worker owns queue/mail
|
|
72
|
+
adapters, and observability owns metrics/tracing setup.
|
|
73
|
+
|
|
47
74
|
## Run
|
|
48
75
|
|
|
49
76
|
```bash
|
|
@@ -52,6 +79,9 @@ make docker-up # local Postgres (postgres:5432)
|
|
|
52
79
|
{{/if}}
|
|
53
80
|
make db-create # create the {{dbName}} database itself (once — safe to re-run)
|
|
54
81
|
go mod tidy
|
|
82
|
+
{{#if worker}}{{#if (eq queue "river")}}make river-migrate # create River's job tables before queued mail
|
|
83
|
+
{{else}}# start Redis and set REDIS_URL before running the worker
|
|
84
|
+
{{/if}}{{/if}}
|
|
55
85
|
make run # APP_ENV=development enables the convenience schema bootstrap
|
|
56
86
|
```
|
|
57
87
|
|
|
@@ -75,17 +105,27 @@ falls back to `docker exec` into whichever container is publishing
|
|
|
75
105
|
other Postgres container{{else}}any Postgres container{{/if}} you already have running.
|
|
76
106
|
|
|
77
107
|
Server listens on `:8080` (override with `PORT`). Ctrl+C = graceful shutdown.
|
|
108
|
+
{{#if worker}}`make dev` starts both the HTTP API and the worker; `make run` starts only the API.
|
|
109
|
+
{{/if}}
|
|
78
110
|
|
|
79
111
|
## Makefile
|
|
80
112
|
|
|
81
113
|
```bash
|
|
82
114
|
make run # go run ./cmd/api
|
|
83
|
-
make
|
|
115
|
+
{{#if worker}}make dev # run cmd/api and cmd/worker together
|
|
116
|
+
make worker # run cmd/worker separately
|
|
117
|
+
{{#if (eq queue "river")}}make river-migrate # create/update River's Postgres job tables
|
|
118
|
+
make river-migrate-test # same River tables in TEST_DB_DSN
|
|
119
|
+
{{else}}# start Redis before running make worker
|
|
120
|
+
{{/if}}
|
|
121
|
+
{{/if}}{{#if auth}}make seed # run one-shot cmd/seed
|
|
122
|
+
{{/if}}make build # go build -o bin/api ./cmd/api
|
|
84
123
|
make test # go test ./...
|
|
85
124
|
make fmt # gofmt -w .
|
|
86
125
|
make vet # go vet ./...
|
|
87
126
|
make lint # golangci-lint run (see .golangci.yml)
|
|
88
127
|
make tidy # go mod tidy
|
|
128
|
+
make tools # install pinned migrate and golangci-lint tools
|
|
89
129
|
make db-create # create the database itself (safe to re-run)
|
|
90
130
|
make db-drop # drop the database
|
|
91
131
|
make migrate-up # apply migrations (reads DB_DSN from ENV_FILE)
|
|
@@ -176,9 +216,17 @@ make docker-up # local postgres first
|
|
|
176
216
|
{{/if}}
|
|
177
217
|
make db-create DB_NAME={{dbName}}_test
|
|
178
218
|
make migrate-up-test
|
|
219
|
+
{{#if (eq queue "river")}}make river-migrate-test
|
|
220
|
+
{{/if}}
|
|
179
221
|
TEST_DB_DSN=postgres://postgres:postgres@localhost:5432/{{dbName}}_test?sslmode=disable REQUIRE_TEST_DB=true go test ./...
|
|
180
222
|
```
|
|
181
223
|
|
|
224
|
+
{{#if (eq queue "river")}}The River integration test also starts a real worker,
|
|
225
|
+
enqueues a JSON job, and waits for its backend-neutral handler. River's own
|
|
226
|
+
schema history is separate from this project's application migrations, and the
|
|
227
|
+
`river-migrate` targets use the pinned River version from `go.mod`.
|
|
228
|
+
{{/if}}
|
|
229
|
+
|
|
182
230
|
## Error payload
|
|
183
231
|
|
|
184
232
|
```json
|
|
@@ -208,32 +256,39 @@ go-scaffold generate module orders
|
|
|
208
256
|
```
|
|
209
257
|
|
|
210
258
|
Scaffolds an `internal/app/order/` module using the project defaults
|
|
211
|
-
(`{{defaultModuleSurface}}` + `{{defaultApplicationStyle}}`), wires its route
|
|
212
|
-
|
|
259
|
+
(`{{defaultModuleSurface}}` + `{{defaultApplicationStyle}}`), wires its route
|
|
260
|
+
into `cmd/api/wiring.go`, and appends a migration file. Add endpoints
|
|
213
261
|
with `generate method`; use `--profile crud` for a CRUD skeleton or
|
|
214
262
|
`--profile cqrs` for separate command/query handlers. See
|
|
215
263
|
`docs/architect/patterns.md` for the module shape and foreign-key rules.
|
|
216
264
|
|
|
217
265
|
## Deploying
|
|
218
266
|
|
|
267
|
+
{{#if worker}}
|
|
219
268
|
Two processes, one image, one target each:
|
|
220
269
|
|
|
221
270
|
```bash
|
|
222
271
|
docker build --target api -t {{projectName}}-api .
|
|
223
|
-
docker build --target worker -t {{projectName}}-worker .
|
|
272
|
+
docker build --target worker -t {{projectName}}-worker .
|
|
224
273
|
```
|
|
225
274
|
|
|
226
275
|
`api` is the last stage, so a plain `docker build .` builds it — the worker
|
|
227
|
-
stage has to be asked for by name
|
|
228
|
-
|
|
276
|
+
stage has to be asked for by name.{{#if auth}} `cmd/seed` is a one-shot
|
|
277
|
+
operational binary run through `make seed`, not a long-running target.{{/if}}
|
|
278
|
+
{{else}}
|
|
279
|
+
The base project has one long-running `cmd/api` image. A worker target becomes
|
|
280
|
+
available after `go-scaffold add worker`; do not deploy a worker that has not
|
|
281
|
+
been generated.
|
|
282
|
+
{{/if}}
|
|
229
283
|
|
|
230
284
|
Both stages are distroless and run as `nonroot`, so there is no shell in either
|
|
231
285
|
image. Debug against the `build` stage instead:
|
|
232
286
|
`docker run --rm -it --entrypoint sh $(docker build -q --target build .)`.
|
|
233
287
|
|
|
234
|
-
`cmd/api` serves HTTP. `cmd/worker` consumes the queue and is a
|
|
235
|
-
deployment with **no port and no health endpoint** — scale it
|
|
236
|
-
and remember that not running it means queued mail is never
|
|
288
|
+
`cmd/api` serves HTTP.{{#if worker}} `cmd/worker` consumes the queue and is a
|
|
289
|
+
separate deployment with **no port and no health endpoint** — scale it
|
|
290
|
+
independently, and remember that not running it means queued mail is never
|
|
291
|
+
sent, silently.{{/if}}
|
|
237
292
|
|
|
238
293
|
Before the first deploy of a release:
|
|
239
294
|
|
|
@@ -22,7 +22,8 @@ import (
|
|
|
22
22
|
// run is this binary's composition root — the only function that sees every
|
|
23
23
|
// module and wires them to each other. Modules never import one another; a
|
|
24
24
|
// domain that needs another's behaviour declares a narrow interface and gets
|
|
25
|
-
//
|
|
25
|
+
// its concrete implementation from the process composition package (see
|
|
26
|
+
// docs/architect/patterns.md).
|
|
26
27
|
//
|
|
27
28
|
// It returns an error rather than calling os.Exit, so every defer below runs
|
|
28
29
|
// on the way out and main() holds the only exit in the binary.
|
|
@@ -55,7 +56,7 @@ func run() error {
|
|
|
55
56
|
|
|
56
57
|
if !cfg.IsProd() {
|
|
57
58
|
// Development boot creates tables but never the schema they live in — each
|
|
58
|
-
// domain gets its own (see model
|
|
59
|
+
// domain gets its own (see the outbound postgres model's TableName), so it has to exist
|
|
59
60
|
// before the bootstrap runs. Production runs the versioned SQL migrations
|
|
60
61
|
// instead, so this entire branch is skipped there.
|
|
61
62
|
// go-scaffold:schemas
|