Guide 5 of 12
Services
A scaffolded resource is CRUD over the store, and most of a Rails app's logic is not CRUD: it is "publish this post", not "update this row". Rails developers reach for a plain Ruby object; here that is bogie g service.
bogie g service publish_post
create app/services/publish_post/publish_post.go
create app/services/publish_post/publish_post_test.go
package publish_post
type store interface{}
type Service struct {
store store
log *slog.Logger
}
func New(store store, log *slog.Logger) *Service {
return &Service{store: store, log: log}
}
func (s *Service) Run(ctx context.Context) error {
s.log.InfoContext(ctx, "publish_post: not implemented yet")
return nil
}
One package per service, named after what it does. ctx is the first parameter of Run (rule 1), and the package knows nothing about HTTP, so a controller, a background job and a test can all call the same function with the same values. The store interface is declared here, next to the code that calls it (rule 2), empty until you list the methods it actually needs:
type store interface {
GetPost(ctx context.Context, id string) (domain.Post, error)
}
*models.Store satisfies whatever you write without this package ever being imported by models, and the fake in the test stays a few lines. Rename Run to say what it does; that is the only thing the generator cannot name for you.
Not wired anywhere
Unlike a controller or a job, a service is never registered above a marker. The thing that needs it declares the dependency and constructs it:
svc := publish_post.New(store, log)
if err := svc.Run(ctx); err != nil { ... }
bogie d service publish_post removes the package. There is no equivalent step to undo in app/application.go, because there was never a step to do there in the first place.
When to reach for one
A plain resource needs no service: a scaffolded controller calling the store directly is the whole story. Reach for g service when an action does more than one thing to the database, calls another system, or is logic a background job and an HTTP handler both need to run identically. If it is CRUD, it belongs in the store; if it is a verb, it belongs in a service.