diff --git a/app/app.go b/app/app.go index e8a9006915..252fcef0e7 100644 --- a/app/app.go +++ b/app/app.go @@ -20,6 +20,9 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) +// App is a pure functional component that does not have any fields, except Server. +// It is a request-scoped struct constructed every time a request hits the server, +// and its only purpose is to provide business logic to Server via its methods. type App struct { srv *Server } diff --git a/app/channels.go b/app/channels.go new file mode 100644 index 0000000000..6ae4a057e6 --- /dev/null +++ b/app/channels.go @@ -0,0 +1,29 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package app + +// Channels contains all channels related state. +type Channels struct { + s *Server +} + +func init() { + RegisterProduct("channels", func(s *Server) (Product, error) { + return NewChannels(s) + }) +} + +func NewChannels(s *Server) (*Channels, error) { + return &Channels{ + s: s, + }, nil +} + +func (c *Channels) Start() error { + return nil +} + +func (c *Channels) Stop() error { + return nil +} diff --git a/app/product.go b/app/product.go new file mode 100644 index 0000000000..cc6c4f55a0 --- /dev/null +++ b/app/product.go @@ -0,0 +1,15 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package app + +type Product interface { + Start() error + Stop() error +} + +var products = make(map[string]func(*Server) (Product, error)) + +func RegisterProduct(name string, f func(*Server) (Product, error)) { + products[name] = f +} diff --git a/app/server.go b/app/server.go index 89b2cd2ed1..34e5ccb47d 100644 --- a/app/server.go +++ b/app/server.go @@ -209,6 +209,8 @@ type Server struct { dndTaskMut sync.Mutex dndTask *model.ScheduledTask + + products map[string]Product } func NewServer(options ...Option) (*Server, error) { @@ -222,6 +224,7 @@ func NewServer(options ...Option) (*Server, error) { licenseListeners: map[string]func(*model.License, *model.License){}, hashSeed: maphash.MakeSeed(), uploadLockMap: map[string]bool{}, + products: make(map[string]Product), } for _, option := range options { @@ -691,6 +694,17 @@ func NewServer(options ...Option) (*Server, error) { s.ShutDownPlugins() } }) + + // Initialize products + for name, initializer := range products { + prod, err := initializer(s) + if err != nil { + return nil, errors.Wrapf(err, "error initializing product: %s", name) + } + + s.products[name] = prod + } + s.AddConfigListener(func(oldCfg, newCfg *model.Config) { if !oldCfg.FeatureFlags.TimedDND && newCfg.FeatureFlags.TimedDND { runDNDStatusExpireJob(app) @@ -985,6 +999,15 @@ func (s *Server) Shutdown() { defer sentry.Flush(2 * time.Second) + // Stop products. + // This needs to happen before because products are dependent + // on parent services. + for name, product := range s.products { + if err := product.Stop(); err != nil { + mlog.Warn("Unable to cleanly stop product", mlog.String("name", name), mlog.Err(err)) + } + } + s.HubStop() s.ShutDownPlugins() s.RemoveLicenseListener(s.licenseListenerId) @@ -1380,6 +1403,14 @@ func (s *Server) Start() error { mlog.Error("Error starting inter-cluster services", mlog.Err(err)) } + // Start products. + // This needs to happen after the server has started. + for name, product := range s.products { + if err := product.Start(); err != nil { + return errors.Wrapf(err, "Unable to start %s", name) + } + } + return nil }