Initial commit.
This commit is contained in:
114
server/internal/application/application.go
Normal file
114
server/internal/application/application.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrApplication indicates that error appeared somewhere in application's lifecycle controller.
|
||||
ErrApplication = errors.New("application")
|
||||
|
||||
// Version is application's version.
|
||||
Version string
|
||||
// Branch is a branch name from which application was built.
|
||||
Branch string
|
||||
// Commit is a commit hash from which application was built.
|
||||
Commit string
|
||||
// Build is a build number.
|
||||
Build string
|
||||
// BuildDate is a date on which application was built.
|
||||
BuildDate string
|
||||
)
|
||||
|
||||
// Application is an application's lifecycle controlling structure.
|
||||
type Application struct {
|
||||
ctx context.Context
|
||||
shutdownDone chan struct{}
|
||||
cancelFunc context.CancelFunc
|
||||
services map[string]Service
|
||||
dataPath string
|
||||
servicesMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates new application's lifecycle controlling structure.
|
||||
func New() *Application {
|
||||
appl := &Application{}
|
||||
appl.initialize()
|
||||
|
||||
return appl
|
||||
}
|
||||
|
||||
// GetContext returns application's global context.
|
||||
func (a *Application) GetContext() context.Context {
|
||||
return a.ctx
|
||||
}
|
||||
|
||||
// GetShutdownDoneChannel returns channel on which lifecycle controller will tell about shutdown completion.
|
||||
// Should be used only in main()!
|
||||
func (a *Application) GetShutdownDoneChannel() chan struct{} {
|
||||
slog.Debug("Returning shutdown completion channel.")
|
||||
|
||||
return a.shutdownDone
|
||||
}
|
||||
|
||||
func (a *Application) initialize() {
|
||||
a.ctx, a.cancelFunc = context.WithCancel(context.Background())
|
||||
a.ctx, a.cancelFunc = signal.NotifyContext(a.ctx, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
a.services = make(map[string]Service)
|
||||
|
||||
a.shutdownDone = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
// Shutdown stops application and all registered services.
|
||||
func (a *Application) Shutdown() error {
|
||||
a.cancelFunc()
|
||||
|
||||
a.servicesMutex.RLock()
|
||||
defer a.servicesMutex.RUnlock()
|
||||
|
||||
for _, service := range a.services {
|
||||
if err := service.Shutdown(); err != nil {
|
||||
slog.Error("Error appeared when trying to shut down service", "service", service.GetName(), "error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
a.shutdownDone <- struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts application and all registered services.
|
||||
func (a *Application) Start() error {
|
||||
go a.signalsListener()
|
||||
|
||||
a.servicesMutex.RLock()
|
||||
defer a.servicesMutex.RUnlock()
|
||||
|
||||
// First pass - connecting dependencies.
|
||||
for _, service := range a.services {
|
||||
if err := service.ConnectDependencies(); err != nil {
|
||||
return fmt.Errorf("%w: connecting dependencies for service '%s': %w", ErrApplication, service.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass - launching startup tasks.
|
||||
for name, service := range a.services {
|
||||
slog.Debug("Launching startup tasks for service", "service", name)
|
||||
|
||||
if err := service.LaunchStartupTasks(); err != nil {
|
||||
return fmt.Errorf("%w: launching startup tasks for '%s': %w", ErrApplication, service.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Debug("Application started.")
|
||||
|
||||
return nil
|
||||
}
|
54
server/internal/application/service.go
Normal file
54
server/internal/application/service.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrServiceAlreadyRegistered appears when trying to register a service with already taken name.
|
||||
ErrServiceAlreadyRegistered = errors.New("service already registered")
|
||||
// ErrServiceNotRegistered appears when trying to get a registered service with unknown name.
|
||||
ErrServiceNotRegistered = errors.New("unknown service")
|
||||
)
|
||||
|
||||
// Service это интерфейс, которому должны соответствовать все сервисы, используемые в приложении.
|
||||
type Service interface {
|
||||
ConnectDependencies() error
|
||||
GetName() string
|
||||
Initialize() error
|
||||
LaunchStartupTasks() error
|
||||
Shutdown() error
|
||||
}
|
||||
|
||||
// GetService возвращает сервис по имени, или же ошибку, если сервис не был зарегистрирован ранее.
|
||||
func (a *Application) GetService(name string) (Service, error) {
|
||||
a.servicesMutex.RLock()
|
||||
service, found := a.services[name]
|
||||
a.servicesMutex.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%w: get service '%s': %w", ErrApplication, name, ErrServiceNotRegistered)
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// RegisterService регистрирует сервис, или возвращает ошибку, если сервис с таким именем уже был
|
||||
// зарегистрирован ранее.
|
||||
func (a *Application) RegisterService(service Service) error {
|
||||
_, found := a.services[service.GetName()]
|
||||
if found {
|
||||
return fmt.Errorf("%w: register service '%s': %w", ErrApplication, service.GetName(), ErrServiceAlreadyRegistered)
|
||||
}
|
||||
|
||||
if err := service.Initialize(); err != nil {
|
||||
return fmt.Errorf("%w: initializing service '%s': %w", ErrApplication, service.GetName(), err)
|
||||
}
|
||||
|
||||
a.servicesMutex.Lock()
|
||||
a.services[service.GetName()] = service
|
||||
a.servicesMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
24
server/internal/application/signals.go
Normal file
24
server/internal/application/signals.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (a *Application) signalsListener() {
|
||||
slog.Info("Starting listening for SIGTERM signal...")
|
||||
|
||||
listener := make(chan os.Signal, 1)
|
||||
|
||||
signal.Notify(listener, syscall.SIGTERM, os.Interrupt)
|
||||
|
||||
<-listener
|
||||
slog.Info("Got SIGTERM, stopping Featurer...")
|
||||
|
||||
if err := a.Shutdown(); err != nil {
|
||||
slog.Error("Something went wrong when trying to shutdown application", "error", err.Error())
|
||||
slog.Error("!!! APPLICATION CANNOT BE STOPPED NORMALLY, KILL IT MANUALLY !!!")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user