package application import ( "context" "net/http" "sync" "go.dev.pztrn.name/metricator/internal/common" "go.dev.pztrn.name/metricator/internal/logger" "go.dev.pztrn.name/metricator/internal/storage" "go.dev.pztrn.name/metricator/internal/storage/memory" ) // Application is a thing that responsible for all application-related // actions like data fetching, storing, etc. on higher level. type Application struct { ctx context.Context storage storage.Metrics config *Config doneChan chan struct{} logger *logger.Logger storageDone chan struct{} httpClient *http.Client name string fetchIsRunningMutex sync.RWMutex fetchIsRunning bool } // NewApplication creates new application. func NewApplication(ctx context.Context, name string, config *Config, logger *logger.Logger) *Application { // Some variables are initialized in initialize() function. // nolint:exhaustivestruct app := &Application{ config: config, ctx: ctx, doneChan: make(chan struct{}), logger: logger, name: name, } app.initialize() return app } // GetDoneChan returns a channel which should be used to block execution until // application's routines are completed. func (a *Application) GetDoneChan() chan struct{} { return a.doneChan } // GetHandler returns HTTP requests handling function. func (a *Application) GetHandler() common.HTTPHandlerFunc { return a.respond } // Initializes internal things like storage, HTTP client, etc. func (a *Application) initialize() { a.storage, a.storageDone = memory.NewStorage(a.ctx, a.name+" storage", a.logger) a.logger.Debugf("Application '%s' initialized with configuration: %+v\n", a.name, a.config) } // Start starts asynchronous things like data fetching, storage cleanup, etc. func (a *Application) Start() { a.storage.Start() go a.startFetcher() // The Context Listening Goroutine. go func() { <-a.ctx.Done() // We should wait until storage routines are also stopped. <-a.storage.GetDoneChan() a.logger.Infoln("Application", a.name, "stopped") a.doneChan <- struct{}{} }() }