initial commit
This commit is contained in:
commit
1253955070
75 changed files with 13972 additions and 0 deletions
113
api/internal/service/service.go
Normal file
113
api/internal/service/service.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Package service holds the business logic. It coordinates the store, the blob
|
||||
// store, the analyzer and the event publisher, and it is the only layer that
|
||||
// knows how the application's features fit together.
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go-places-api/internal/analysis"
|
||||
"go-places-api/internal/blobstore"
|
||||
"go-places-api/internal/config"
|
||||
"go-places-api/internal/events"
|
||||
"go-places-api/internal/store"
|
||||
)
|
||||
|
||||
// Errors surfaced to the transport layer. The HTTP and GraphQL layers map these
|
||||
// onto status codes and GraphQL error extensions.
|
||||
var (
|
||||
// ErrNotFound means the addressed entity does not exist.
|
||||
ErrNotFound = errors.New("not found")
|
||||
// ErrInvalidInput means the request was malformed or violated a rule.
|
||||
ErrInvalidInput = errors.New("invalid input")
|
||||
// ErrTooLarge means the upload exceeded the configured limit.
|
||||
ErrTooLarge = errors.New("upload too large")
|
||||
// ErrUnsupportedType means the upload's content type is not allowed.
|
||||
ErrUnsupportedType = errors.New("unsupported media type")
|
||||
)
|
||||
|
||||
// Service is the application entry point used by the transport layers.
|
||||
type Service struct {
|
||||
store store.Store
|
||||
blobs blobstore.BlobStore
|
||||
analyze analysis.Analyzer
|
||||
events events.Publisher
|
||||
cfg config.Config
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// Logger is the small logging surface the service needs. Keeping it narrow
|
||||
// means tests can pass a no-op without pulling in slog.
|
||||
type Logger interface {
|
||||
Warn(msg string, args ...any)
|
||||
Info(msg string, args ...any)
|
||||
}
|
||||
|
||||
// Options configures a Service.
|
||||
type Options struct {
|
||||
Store store.Store
|
||||
Blobs blobstore.BlobStore
|
||||
Analyzer analysis.Analyzer
|
||||
Events events.Publisher
|
||||
Config config.Config
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// New builds a Service, filling in no-op collaborators where none are supplied
|
||||
// so a partially wired service still works in tests.
|
||||
func New(opts Options) *Service {
|
||||
s := &Service{
|
||||
store: opts.Store,
|
||||
blobs: opts.Blobs,
|
||||
analyze: opts.Analyzer,
|
||||
events: opts.Events,
|
||||
cfg: opts.Config,
|
||||
logger: opts.Logger,
|
||||
}
|
||||
if s.analyze == nil {
|
||||
s.analyze = analysis.NewLocal(analysis.DefaultMaxDimension)
|
||||
}
|
||||
if s.events == nil {
|
||||
s.events = events.NewLogPublisher(nil)
|
||||
}
|
||||
if s.logger == nil {
|
||||
s.logger = nopLogger{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pinger is implemented by stores that can report whether they are reachable.
|
||||
// It is checked by the readiness probe, and is optional so an in-memory fake
|
||||
// still satisfies the contract without implementing it.
|
||||
type Pinger interface {
|
||||
PingContext(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Ping reports whether the service's dependencies can serve traffic. A store
|
||||
// that cannot report its health is treated as healthy.
|
||||
func (s *Service) Ping(ctx context.Context) error {
|
||||
p, ok := s.store.(Pinger)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p.PingContext(ctx)
|
||||
}
|
||||
|
||||
// mapStoreError translates store sentinels into service sentinels.
|
||||
func mapStoreError(err error, entity string) error {
|
||||
switch {
|
||||
case errors.Is(err, store.ErrNotFound):
|
||||
return fmt.Errorf("%w: no such %s", ErrNotFound, entity)
|
||||
case errors.Is(err, store.ErrConflict):
|
||||
return fmt.Errorf("%w: conflicting %s", ErrInvalidInput, entity)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type nopLogger struct{}
|
||||
|
||||
func (nopLogger) Warn(string, ...any) {}
|
||||
func (nopLogger) Info(string, ...any) {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue