一键导入
arc-project-architecture-conventions
Apply backend architecture, DIP, usecase result boundaries, zap logging, Go constants, and helper limits.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Apply backend architecture, DIP, usecase result boundaries, zap logging, Go constants, and helper limits.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use .ai-code-index for local search, symbols, profiles, files, stats, refresh, and diagnostics.
Read-only project/AppSec audit: assets, data map, vuln review; Lark risks via arc:docs.
Local SAST/SCA/secrets/DAST automation with data-value re-ranking and Arc handoffs.
Current-state task docs; security/audit handoff to detailed subtasks with roles and caliber.
Add low-cost fmt/time or log.Printf timing probes to Go Gin SSR request paths.
Apply Chinese comment conventions; avoid noisy parameter/return boilerplate on obvious usecase contracts.
| name | arc:project-architecture-conventions |
| description | Apply backend architecture, DIP, usecase result boundaries, zap logging, Go constants, and helper limits. |
Use this skill before writing, changing, or reviewing backend, service, controller, repository, infrastructure, helper, logging, debugging, constants, enum-like states, or project skeleton code. Project code must follow the default backend architecture, the Dependency Inversion Principle (DIP), and the naming, layering, file, interface, logging, debugging, constant, helper limit, and observability conventions defined here.
For new backend modules and project skeletons, use this architecture by default. For existing repositories, preserve established local patterns unless the task explicitly asks to migrate toward this architecture.
arc:code-comment-conventions when adding comments to functions, controllers, or implementation steps.references/backend-architecture.md when deeper file-level or interface-level guidance is needed.func declarations._helpers.go when an original file reaches two private functions, such as order.go -> order_helpers.go._helpers.go files; split by focused behavior instead of growing a dump file.ponytail skill from the local environment.$ponytail, use that skill first.SKILL.md before code edits. Known fallback locations include ~/.claude/plugins/marketplaces/ponytail/skills/ponytail/SKILL.md and ~/.codex/.tmp/marketplaces/ponytail/skills/ponytail/SKILL.md.ponytail cannot be found or loaded, report that explicitly before editing code; do not invent or infer ponytail rules.ponytail conflicts with this skill, stop and report the conflict instead of silently choosing one rule.ponytail rejects unrequested abstractions such as an interface with one implementation. This skill explicitly requests DIP boundary interfaces, so interfaces are allowed only when they protect business logic from infrastructure, external capabilities, framework details, transport boundaries, or cross-layer dependencies.
Resolution rules:
Contract, domain/repositories interfaces, or capability contracts that cross real boundaries; do not add interfaces for every struct or helper.All project code must obey DIP:
usecase/<module> owns application/business behavior and consumes domain/repositories interfaces or explicit capability contracts.internal/wire; it is not constructed inside business logic.Use this backend architecture as the default for Go backend projects. Names may be adapted only when the host language or existing repository convention requires it; responsibilities and dependency direction must stay the same.
backend/
├── cmd/server/
├── configs/
├── internal/
│ ├── bootstrap/
│ ├── constants/
│ ├── domain/{entities,events,filters,repositories,services}/
│ ├── usecase/<module>/
│ ├── interface/restful/{controllers,dto/{requests,responses},middlewares,router/routes}/
│ ├── infrastructure/{gateways,support}/
│ └── wire/
└── migrations/{up,down}/
The domain directory is a strict whitelist: use only entities, events, filters, repositories, and services unless the host repository already has a deliberate stronger convention. Do not create internal/domain/gateways, internal/domain/ports, internal/domain/adapters, or internal/domain/clients to hold external integration interfaces. gateways is an infrastructure concept in this architecture.
Layer responsibilities:
domain/entities: Business objects with identity, lifecycle state, and domain invariants. Entity is not DTO and not ORM model. Put entity-local business behavior here when it protects invariants, such as status transitions or validation that belongs to one aggregate. Entities must not contain transport conversion methods such as ToDTO, ToResponse, or methods returning dto/responses types.domain/repositories: Persistence ports consumed by usecases. Define business persistence needs here; do not mention SQL tables, Mongo collections, HTTP, or driver types.domain/services: Pure domain operations that do not naturally belong to one entity, especially rules involving multiple entities. Do not use this as an application workflow bucket.usecase/<module>: Application/business workflows and transaction orchestration. Modules use contract.go, main.go, params.go, results.go, optional errors.go, and focused service*.go files. Contract is the controller-facing interface; Service implements it and depends on domain/repositories plus explicit external capability contracts owned by the usecase module. Controller-facing Contract methods return named usecase result types from results.go; do not return raw domain/entities from some methods and *Result types from others.interface/restful/controllers: HTTP boundary. Controllers bind input, authorize, call usecase contracts, map errors, map entity/usecase results to response DTOs, and respond. Controllers must not touch repositories or database drivers directly.interface/restful/dto: Transport schema only. Request DTOs describe incoming HTTP bodies/queries; response DTOs describe wire output. Do not put entity/usecase-to-DTO mapping constructors, factories, or business helpers there.infrastructure/gateways: Concrete external gateways such as Postgres persistence, notification, storage, and recommendation. Persistence uses database models for storage shape and repository implementations for domain/repositories. Repositories translate between storage models and domain entities.infrastructure/support: Cross-cutting support capabilities such as authorization, cache, logger, and security. Use contract.go, engine.go, service.go, and main.go to separate service contracts from concrete engines.wire: Composition root. Construct repositories, support services, usecases, controllers, bootstrap, seeds, and application lifecycle. It may import concrete infrastructure; business layers may not.bootstrap: Startup domain initialization such as ensuring seed data or super-admin prerequisites after migrations and repository construction.Keep these three structures separate:
interface/restful/dto, may follow API naming and JSON shape, and must not contain business rules or storage tags beyond transport binding.domain/entities, carries identity, lifecycle state, domain behavior, and invariants, and must not depend on HTTP, JSON, DB, ORM, or transport DTOs.infrastructure/gateways/persistence/<store>/models, may contain ORM/DB tags and storage-optimized fields, and must not contain business behavior.Conversion ownership:
usecase.FindByID returns *entities.Order; Save accepts *entities.Order.Mapper placement:
internal/mapper that import REST DTOs, domain entities, and repository models together. That package knows every layer and becomes a dependency knot.DTO -> Entity, Entity -> DTO, Entity -> Model, and Model -> Entity in one mapper file. That file crosses transport, domain, and persistence boundaries.internal/interface/restful/controllers/order_mapper.go.internal/infrastructure/gateways/persistence/postgres/repository/order_mapper.go.For Go REST APIs, plan response bodies as named DTO compositions. Do not return dynamic maps or catch-all envelopes from controllers.
Define the shared response base in internal/interface/restful/dto/responses/base.go; if the host repository already uses singular dto/response, keep that local package name instead of renaming only for this rule. Preserve the repository's established base type name, such as Base, BaseResponse, or ResponseBase.
type Base struct {
Success bool `json:"success"`
Message string `json:"message"`
}
var SuccessBase = Base{
Success: true,
Message: "ok",
}
Every endpoint response must define its own named response struct in the response DTO package. Embed Base and use explicit concrete fields.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type UserResp struct {
Base
Data User `json:"data"`
}
type UserListResp struct {
Base
Data []User `json:"data"`
}
type Page struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
}
type UserPageResp struct {
Base
Data []User `json:"data"`
Page Page `json:"page"`
}
type UserCursorResp struct {
Base
Data []User `json:"data"`
Cursor pagination.CursorResponse `json:"cursor"`
}
Use Base: SuccessBase directly in the controller response literal.
Rules:
base.go contains only the base envelope and base constants/helpers, meta.go contains shared response metadata such as Meta, Pagination, cursor, or page structs, and feature files such as activity_category.go contain only that resource's DTOs and endpoint response structs.BaseResponse, Meta, Pagination, and feature DTOs in one feature response file. Shared schema belongs in shared files.Data must be a concrete DTO type or slice of a concrete DTO type, such as User or []User.Page Page metadata (page, page_size, total); cursor pagination uses cursor metadata (next_cursor, prev_cursor, has_more). Do not mix the two contracts.github.com/iwen-conf/utils-pkg/pagination when available: CursorRequest, CursorResponse, and NewHMACCodec for cursor pagination; OffsetRequest and OffsetResponse for offset/limit APIs. If the public API is page/page_size, map it explicitly to query offset/limit instead of exposing cursor fields.any, interface{}, map[string]any, gin.H, anonymous structs, or generic catch-all envelopes such as Response[T any].internal/domain, internal/usecase, repositories, database models, Gin, or database drivers for mapping. Keep DTO files as named wire-contract structs plus harmless envelope constants/types.dto/responses or expose response conversion methods. Do not add methods like func (a ActivityCategory) ToDTO() responses.ActivityCategoryDTO; that reverses the dependency direction.internal/interface/restful/controllers or a focused mapper file in that package. Do not add functions like responses.NewUser(entity) or responses.NewUserList(usecaseResult).Keep controller-facing usecase contracts stable and application-shaped. A Contract method should accept named Param types and return named Result types plus error, even when the service internally loads or mutates a single domain entity.
Rules:
params.go and return payloads in results.go.*entities.Xxx, []*entities.Xxx, repository models, REST DTOs, or database driver types from Contract methods.Contract; a method set like GetNovel(...) (*entities.Novel, error) plus ListNovels(...) (*ListNovelsResult, error) is inconsistent.Example:
// Bad: controller-facing contract leaks a domain entity and mixes return styles.
type BadContract interface {
GetNovel(ctx context.Context, id int64) (*entities.Novel, error)
ListNovels(ctx context.Context, query ListNovelsParam) (*ListNovelsResult, error)
}
// Good: all controller-facing returns are usecase result types.
type Contract interface {
GetNovel(ctx context.Context, param GetNovelParam) (*NovelResult, error)
ListNovels(ctx context.Context, query ListNovelsParam) (*ListNovelsResult, error)
}
When a usecase needs payment, notification, storage, recommendation, search, moderation, or another external capability, define the narrow capability interface in the usecase package that consumes it, or in internal/usecase/shared only after real reuse. Concrete adapters live under internal/infrastructure/gateways/<capability> or internal/infrastructure/support/<capability> and are wired in internal/wire.
Rules:
internal/domain/gateways for external capability ports. That package name is forbidden in this architecture.domain/repositories; repositories are persistence ports.PaymentProcessor, Notifier, ObjectStore, SearchClient, or a repository-local established name over Gateway.New(...); only wire connects the concrete gateway to the usecase contract.Example:
// Bad: domain/gateways is a fake layer and turns domain into an integration bucket.
package gateways
type PaymentGateway interface {
CreateCheckoutSession(ctx context.Context, orderID int64) (string, error)
}
// Good: the consuming usecase owns the narrow capability it needs.
package order
type PaymentProcessor interface {
CreateCheckoutSession(ctx context.Context, orderID int64) (*CheckoutSessionResult, error)
}
Use this dependency direction:
cmd -> internal/wire -> internal/interface/restful -> internal/usecase -> internal/domain
internal/infrastructure -> internal/domain
Forbidden imports:
internal/domain -> internal/infrastructure, internal/usecase, internal/interface, framework/driver SDKs
internal/usecase -> gin, net/http, pgx, database/sql, internal/interface
internal/interface/restful/controllers -> domain/repositories, pgx, database/sql, pgxpool
internal/interface/restful/dto -> internal/domain, internal/usecase, internal/infrastructure, framework/driver SDKs
Keep object creation and wiring in internal/wire, main, or the project composition root:
func main() {
app, err := wire.NewApplication()
if err != nil {
panic(err)
}
if err := app.Start(); err != nil {
panic(err)
}
}
Rules:
wire / main may know concrete implementations because it is the injection point.usecase/<module>.New(...) must accept domain repository interfaces and explicit capability contracts, not concrete adapters.Repo, so the field name carries the dependency role without a redundant comment. Preserve Go visibility from the local pattern: use novelCommentRepo repositories.NovelComment for private service fields, or NovelCommentRepo repositories.NovelComment only when the field is intentionally exported. Do not use vague names such as comments, reports, or readingHistory when the type is a repository contract.sql.Open, SDK constructors, HTTP client setup, or queue/cache constructors.Repository field naming example:
// Bad: vague field names need comments to explain the role.
type BadService struct {
comments repositories.NovelComment
readingHistory repositories.NovelReadingHistory
}
// Good: private service fields keep Go visibility local.
type Service struct {
novelCommentRepo repositories.NovelComment
readingHistoryRepo repositories.NovelReadingHistory
}
// Good when the repository's local pattern intentionally exports injected fields.
type ExportedService struct {
NovelCommentRepo repositories.NovelComment
ReadingHistoryRepo repositories.NovelReadingHistory
}
Use zap as the default structured logging backend for Go services, but add logs only where they improve diagnosis, auditability, or operational visibility.
Architecture rules:
go.uber.org/zap; do not mix zap with log, slog, logrus, or ad hoc fmt.Println logging.cmd, wire, or infrastructure/support/logger; call Sync() during graceful shutdown when appropriate.domain pure: no zap imports and no logging in entities, value objects, filters, or pure domain services.usecase code needs logs, prefer a narrow logger contract or the repository's existing project logger abstraction. Import zap directly there only when the repository already standardizes on direct zap injection.operation, request_id, tenant_id, user_id, resource, resource_id, status, duration_ms, and error..arc/artifacts/<task>/logs/backend.log or tmp/logs/backend.log. Do not rely only on terminal scrollback or memory.Log these when business or operations benefit:
Do not log these by default:
Level rules:
Debug: development-only diagnostics or sampled details that can be disabled in production.Info: successful lifecycle events and meaningful business state changes.Warn: recoverable anomalies, retries, throttling, suspicious but handled security events, and slow operations above the project threshold.Error: failed operations that require attention and are not normal user input outcomes.Fatal/Panic: only at process boundaries when the service cannot continue safely.Debugging evidence rules:
For Go code, constants are compile-time semantic names, not C-style global macros. Prefer the narrowest useful scope, idiomatic names, untyped literals when flexibility is useful, and typed custom constants when modeling domain state.
MixedCaps / mixedCaps. Do not use SNAKE_CASE or ALL_CAPS; export only when another package should depend on the name.time constants when available, such as time.DateTime, time.DateOnly, time.TimeOnly, time.RFC3339, and time.RFC3339Nano.constants.go files; use internal/constants only for truly application-wide constants with multiple legitimate consumers.const values, usually with a zero Unknown or default value. Do not pass naked int or string values as domain states.paymentgateway.Status("unknown"), and do not add package aliases such as paymentgateway when the declared package name can be used directly. For example, in a payment gateway package that exports PaymentStatusUnknown, use payment.PaymentStatusUnknown, not gateways.PaymentStatusUnknown or paymentgateway.Status("unknown").String() for enum-like states used in logs, errors, metrics, serialization diagnostics, or operator-facing output. Add parse/validate helpers when values enter from transport or storage.Enum constant reference example:
// Bad: raw casts, broader package detours, and needless import aliases hide ownership.
Status: paymentgateway.Status("unknown")
Status: gateways.PaymentStatusUnknown
// Good: use the package that owns the status type and constants.
Status: payment.PaymentStatusUnknown
Follow this helper placement:
internal/usecase/<module> package, using focused files such as helpers.go, service_<feature>.go, or services.go when the module already uses them.internal/usecase/shared; do not duplicate them in each feature module.internal/interface/restful/dto/requests and response payloads in internal/interface/restful/dto/responses; controllers should return named DTO structs instead of declaring private DTO structs inline for runtime responses. Keep DTO packages schema-only and free of entity/usecase mapper constructors.dto/responses with embedded Base and explicit concrete fields such as Data User, Data []User, Page Page, or Cursor pagination.CursorResponse; do not define ad hoc response structs inside controllers.internal/interface/restful/controllers only when they are transport-boundary helpers. Move pure business helpers down into usecase.common, misc, tools, or broad utils buckets. Extract only after real reuse and with a specific package purpose.usecase/<module>, not controllers, infrastructure, or wire.domain/repositories or explicit capability contracts instead of concrete infrastructure.internal/wire and implements domain or capability contracts.internal/domain/gateways, internal/domain/ports, internal/domain/adapters, or internal/domain/clients package exists; external capability contracts consumed by usecases are owned by usecase packages.Repo, such as novelCommentRepo repositories.NovelComment or intentionally exported NovelCommentRepo repositories.NovelComment, instead of vague plural nouns plus explanatory comments.contract.go contains the exported controller-facing contract, not concrete service or adapter logic, and its methods return named usecase result types instead of raw domain entities.not found only when the product flow needs a missing-resource error state.MixedCaps / mixedCaps, stay near their business context, prefer untyped literals unless typing is semantically required, and avoid broad constants.go buckets.time.DateTime; raw equivalent strings are not accepted.Unknown or default zero value, and cross-service constants come from versioned generated contracts or governed shared modules.Status("unknown").<original>_helpers.go sibling files.helpers are business-local unless proven reusable.internal/usecase/shared or another focused package and do not import interface or infrastructure packages.dto/responses.Base composition plus per-endpoint named response structs; Data uses concrete DTO types or slices, page and cursor metadata stay separate, no response body uses any, interface{}, map[string]any, gin.H, anonymous structs, or Response[T any], and DTO packages do not contain entity/usecase mapping constructors.ponytail was read before coding, or its absence was reported before editing.