Documentation ¶
Overview ¶
Package appuc contains the application UseCase which supports the settings fetching and updating requests, allows the application to be reloaded based on the mutable settings which are stored in the database, and maintains and provides visible settings and use case objects (with atomic replacement support) so they may be used by the resources packages.
Index ¶
- type Builder
- type Option
- type SettingsConnQueryer
- type SettingsQueryer
- type SettingsRepo
- type SettingsTxQueryer
- type UseCase
- func (app *UseCase) CarsUseCase() *carsuc.UseCase
- func (app *UseCase) Reload(ctx context.Context) error
- func (app *UseCase) Settings() (vs *model.VisibleSettings, minb, maxb *model.Settings)
- func (app *UseCase) UpdateSettings(ctx context.Context, s *model.Settings) (vs *model.VisibleSettings, minb, maxb *model.Settings, err error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder interface { // NewAppUseCase creates a new application use case. This use case // needs a SettingsRepo in order to fetch or update mutable settings // from the database. It also needs to take all repository instances // which may be required by other use cases (e.g., a repo.Cars) // because it needs to pass them to the Builder instance again after // reloading or updating settings, changing the mutable settings in // the database and memory. // // When settings are updated and a new Builder instance is obtained, // it can be asked to create new use case objects. The fields of // the new application UseCase can be copied into the previous // UseCase instance, updating it in-place, while other use case // objects (e.g., carsuc.UseCase) can replace their old instance // as an opaque object. This replacement strategy requires the // resources packages to ask this application UseCase for the actual // use case objects, right before using them, so they can be fetched // or updated atomically as managed by the application UseCase. NewAppUseCase( p repo.Pool, s SettingsRepo, carsRepo repo.Cars, ) (*UseCase, error) // NewCarsUseCase creates a new carsuc UseCase object having the // provided database connection pool and cars repository. NewCarsUseCase(p repo.Pool, r repo.Cars) (*carsuc.UseCase, error) }
Builder interface represents the expectations from the application use case builders. All use cases which can be instantiated by a configuration struct have one NewX method here which takes database connection pool and their repository packages dependencies. All versions of configuration structs may be used by the migration use case, however, only the last supported version may be used by normal non-migration use cases. The last version of configuration struct must implement this interface, take repository packages and create use case objects based on its contained settings. When new settings are loaded from a database, they may override some of the configuration settings, hence, produce a new Builder instance.
type SettingsConnQueryer ¶
type SettingsConnQueryer interface { SettingsQueryer // Fetch queries the mutable settings from the settings repository, // deserializes them, merges them into a clone of the base settings // (representing the configuration file and environment variables // state when the settings repository instance was created), and // returns the fresh configuration instance as a Builder interface // in addition to its visible settings (as an instance of the // version-independent model.VisibleSettings struct). // // The settings boundary values are also returned as `minb` and // `maxb` instances (of the version-independent model.Settings // struct), taken from the base settings. In order to sync these // boundary values from the base settings to the database (so they // can be queried by other components from the database), it is // required to perform a migration or use the Update method of the // SettingsTxQueryer interface instead. In other words, Fetch // neither updates the database nor verifies it beyound the version // of the persisted configuration settings. // If the database settings were out of the acceptable range of // values, they will take the nearest (minimum or maximum) boundary // value and that adjustment will be logged as a warning. // // Fetch requires a connection because the new Builder instance // may not be created but after a successful commit of the mutable // settings fetching query. It is also reified by the minor-version // specific schema loading packages (i.e., some schXvY package) // which expects a connection instance as they are used before the // migration process may begin (which depends on an updated instance // of the configuration settings). Fetch(ctx context.Context) ( b Builder, vs *model.VisibleSettings, minb, maxb *model.Settings, err error, ) }
SettingsConnQueryer interface indicates queries which require a database connection for their execution specifically (i.e., they need to indicate the transactions boundaries themselves and may not tolerate execution of extra queries before or after their own queries in a single transaction) and also queries which may be executed with either a connection or an ongoing transaction (by embedding the common SettingsQueryer interface).
type SettingsQueryer ¶
type SettingsQueryer interface { }
SettingsQueryer interface indicates queries which can be executed on a settings repository either with a connection or an ongoing transaction. This interface is embedded by both of SettingsTxQueryer and SettingsConnQueryer interfaces. There is no such methods in this version.
type SettingsRepo ¶
type SettingsRepo interface { // Conn wraps the provided connection instance and creates a new // settings repository connection-based queryer. Conn(repo.Conn) SettingsConnQueryer // Tx wraps the provided transaction instance and creates a new // settings repository transaction-based queryer. Tx(repo.Tx) SettingsTxQueryer }
SettingsRepo specifies the settings repository expectations, allowing version-independent mutable settings to be converted, serialized, and stored in the database or queried from the database, deserialized as a version-dependent mutable settings struct, and converted back to a version-independent struct again. In both paths of memory-database updating/fetching paths, a clone of the base configuration settings (which must be passed to and kept in the settings repository instance during its instantiation) must be updated by application of the new mutable settings. The base settings are usually read from a configuration file and some of them may be overridden by relevant environment variables. When base settings are overridden by the in-database mutable settings, result is returned to the use cases layer as an instance of the Builder interface, so they may be used for creation of new use case objects.
type SettingsTxQueryer ¶
type SettingsTxQueryer interface { SettingsQueryer // Update converts the version-independent mutable model.Settings // instance into a version-dependent serializable settings instance // for the last supported version, serializes them as JSON, and // then stores them in the settings repository. Given mutable // settings are also used in order to update a clone of the base // settings. Updated configuration settings will be returned as an // instance of the Builder interface in addition to its visible // settings (which are provided as an instance of the // version-independent model.VisibleSettings struct). // // The settings boundary values are also returned as `minb` and // `maxb` instances (of the version-independent model.Settings // struct), taken from the base settings. The argument `s` settings // must fall in this acceptable range of values, otherwise, an error // will be returned and settings will be kept unchanged. // When updating the database with new settings, the boundary values // will be serialized and stored alongside them too. // // Update requires an open transaction because it is supposed to // be reified by some major-version specific schema migration // settler object (i.e., some stlmigN package) which expects a // transaction instance (as they are used for the last phase of // a multi-database migration operation). Update(ctx context.Context, s *model.Settings) ( b Builder, vs *model.VisibleSettings, minb, maxb *model.Settings, err error, ) }
SettingsTxQueryer interface indicates queries which require an open database transaction for their execution specifically (i.e., they expect other queries to be executed before or after their own queries and so must take an open transaction which is committed or rolled back by their caller as appropriate) and also queries which may be executed with either an ongoing transaction or a connection (by embedding the common SettingsQueryer interface).
type UseCase ¶
type UseCase struct {
// contains filtered or unexported fields
}
UseCase represents an application use case. It holds a database connection pool, settings repository instance, and all repository instances which are required by other supported use cases. Therefore, it can pass these repository instances to a use case builder object (which is realized by the effective Config instance) in order to create supported use case objects (during a Reload or UpdateSettings operation).
func New ¶
New instantiates an application use case object. The Reload method of this object should be called at least once, so it can create other supported use case objects, before their corresponding getter methods are invoked (otherwise, they may return nil).
func (*UseCase) CarsUseCase ¶
CarsUseCase returns the currently effective cars use case object. At least one of Reload or UpdateSettings methods must be called before this (and other use case objects getter methods) may be called.
func (*UseCase) Reload ¶
Reload queries the settings repository in order to fetch the current effective mutable settings. Those settings will override the base settings which were read from a configuration file (and possibly overridden by environment variables) in order to create a fresh Builder instance. Thereafter, that Builder instance will be used for creation of fresh use case objects, including the application use case itself. Ultimately, new visible settings and use case objects will be changed atomically to their fresh values. Despite other use case objects which can be replaced by new instances, whenever the application use case had some dependency on settings, we have to take those relevant fields from the new appuc UseCase instance and update the `app` instance fields in-place. The reason is that other use case objects are fetched by resources packages before each request (using a synchronized getter of this application use case instance), however, there is no method to replace the `app` instance itself.
UpdateSettings and Reload methods are synchronized using a mutex so only one long-running attempt for querying/updating the mutable settings may exist, while other goroutines may fetch the old settings and use case objects without any blocking. When the operation could complete successfully and new use case objects were created, a second read-write lock will be used in order to pause other goroutines and switch all use case objects to new instances. The order of these locks ensures a deadlock-free implementation.
func (*UseCase) Settings ¶
func (app *UseCase) Settings() ( vs *model.VisibleSettings, minb, maxb *model.Settings, )
Settings returns a pointer to the shared instance of visible settings which are currently in effect, in addition to the settings boundary values which indicate the minimum/maximum acceptable values. If caller needs to modify them, those structs must be deeply cloned. The effective settings and use case objects which are built based on them (and other invisible settings) may be updated atomically, while they are exposed by a series of getter methods. At least one of Reload or UpdateSettings methods must be called before this (and other use case objects getter methods) may be called.
func (*UseCase) UpdateSettings ¶
func (app *UseCase) UpdateSettings( ctx context.Context, s *model.Settings, ) (vs *model.VisibleSettings, minb, maxb *model.Settings, err error)
UpdateSettings updates the mutable settings in the database, with help of a settings repository instance, according to the given `s` settings. This leads to preparation of a fresh Builder instance which can be used for creation of fresh use case objects, including the application use case itself. Thereafter, new visible settings and use case objects will be changed atomically to their fresh values. Despite other use case objects which can be replaced by new instances, whenever the application use case had some dependency on settings, we have to take those relevant fields from the new appuc UseCase instance and update the `app` instance fields in-place. The reason is that other use case objects are fetched by resources packages before each request (using a synchronized getter of this application use case instance), however, there is no method to replace the `app` instance itself.
UpdateSettings and Reload methods are synchronized using a mutex so only one long-running attempt for querying/updating the mutable settings may exist, while other goroutines may fetch the old settings and use case objects without any blocking. When the operation could complete successfully and new use case objects were created, a second read-write lock will be used in order to pause other goroutines and switch all use case objects to new instances. The order of these locks ensures a deadlock-free implementation.
The returned `vs` vissible settings and `minb` and `maxb` which are minimum/maximum boundary settings values are pointers to the shared structs. If caller needs to modify them, those structs must be deeply cloned beforehand.