service-layer

Implement service-layer boundaries with domain-shaped interfaces and injected dependencies.

4|1|Updated Apr 12, 2026
One-click install
npx skills add https://github.com/reliant-labs/forge --skill service-layer-reliant-labs
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: service-layer
Source: https://github.com/reliant-labs/forge/tree/main/internal/templates/project/skills/forge/service-layer
Command: npx skills add https://github.com/reliant-labs/forge --skill service-layer-reliant-labs

SYSTEM DOCUMENTATION & REQUIREMENTS

What problem does it solve?

It standardizes how to structure business logic in a forge codebase so domain operations stay independent from wire (API) and storage (DB) concerns, and so errors map consistently to client-facing responses.

Core Features & Use Cases

  • Defines the service boundary: places a Service interface in internal/<svc>/contract.go using domain-shaped inputs and outputs.
  • Encourages dependency injection: uses a Deps struct and a New(deps Deps) Service constructor to inject DB handles and deterministic helpers like time.Now and ID generators.
  • Implements a typed error domain: routes storage/driver failures into shared forge/pkg/svcerr sentinels and uses typed errors that Unwrap() to the corresponding sentinel for correct handler mapping.
  • Keeps observability at the handler edge: applies logging, tracing, metrics, recovery, and request-id uniformly via forge/pkg/observe interceptors rather than sprinkling middleware wrappers through the service.
  • Supports testable design: enables unit testing at the service boundary with injected deps, and generates mocks from the service interface for consumers (handlers and other services).

Quick Start

Write an internal/<svc>/contract.go that declares Service methods with domain inputs/outputs, then implement them in internal/<svc>/service.go using an injected Deps struct and return svcerr-backed errors for not-found, invalid-argument, and already-exists cases.

Frequently Asked Questions about service-layer

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I structure a Golang service layer to keep domain logic independent from API and database types?

To structure a Golang service layer with clean boundaries, define a Service interface in contract.go using domain-shaped inputs and outputs, then implement it in service.go with a Deps struct for injecting database handles and deterministic helpers like time and ID generators.

What is the best way to handle domain errors in a Go service layer before they reach the API handler?

The best way to handle domain errors in a Go service layer is to route storage and driver failures into shared sentinel errors using a dedicated error package, and return typed errors that unwrap to those sentinels so handlers can map them consistently to client-facing responses.

How do I make a Golang service layer testable with dependency injection?

To make a Golang service layer testable, use dependency injection by passing a Deps struct to the service constructor, injecting database handles and deterministic helpers like time.Now and ID generators so unit tests can run at the service boundary without external dependencies.

Where should observability like logging and tracing live in a Go service layer architecture?

Observability like logging, tracing, and metrics should live at the handler edge rather than inside the service layer, applied uniformly via interceptors so middleware wrappers are not sprinkled through the domain business logic.

Does a Go service layer need a separate contract file for its interface?

Yes, a Go service layer needs a separate contract file to declare the Service interface with domain-shaped method signatures, keeping the domain operations callable by handlers while remaining independent from proto and DB row types.

Why do typed errors need to unwrap to sentinel errors in a Go service layer?

Typed errors need to unwrap to sentinel errors in a Go service layer so that handlers can correctly map storage and driver failures to client-facing responses, keeping error handling consistent across the domain boundary without leaking internal error types.