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 }