Documentation
¶
Overview ¶
Package ioc provides an Inversion of Control component container and lifecycle hooks.
This package provides the types that define and support Granitic component container, which allows your application's objects and Granitic's framework facilities to follow the inversion of control (IoC) pattern by having their lifecycle and dependencies managed for them. An in-depth discussion of Granitic IoC can be found at https://granitic.io/ref/component-definition-files but a description of the core concepts follows.
Components ¶
A component is defined by Granitic as an instance of a Go struct with a name that is unique within an application. Each component in your application requires a entry in the components section of your application's component definition file like:
{ "components": { "componentName": { "type": "package.structType" } } }
e.g:
{ "components": { "createRecordLogic": { "type": "inventory.CreateRecordLogic" } } }
For complete information on defining components, refer to https://granitic.io/ref/component-container
Granitic's documentation will use the term component and component instance interchangeably. For example, 'component field' means 'a field on the instance of the Go struct associated with that component'.
The container ¶
When Granitic starts, it will create an instance of ioc.ComponentContainer - a structure that holds references to each of the components in your application. It is responsible for injecting dependencies and configuration into your components (see below) and managing lifecycle events (also see below). The container is also referred to as the component container or IoC container.
Framework components ¶
For each Granitic facility that you enable in your application, one or more framework components will be created and added to the container. A framework component is exactly the same as any other component - an instance of a struct with a name. Depending on the complexity of the facility, multiple components may be created.
A very simple name-spacing is used to separate the names of framework components from application components - framework components' names all start with the string grnc
You are strongly encouraged to make sure your application components' names do not start with this string.
Dependencies and configuration ¶
As part of its definition, your components can request that other components are injected into its fields. Your definition can also include configuration (actual values to be set when the component is instantiated) or configuration promises (values to be injected once all sources of configuration have been merged together).
{ "components": { "createRecordLogic": { "type": "inventory.CreateRecordLogic", "MaxTracks": 20, "ArtistMustExist": "conf:record.disableAutoArtist", "DAO": "ref:inventoryDAO" } } }
In the above example, the field CreateRecordLogic.MaxTracks is set to 20 when the struct is instantiated, ArtistMustExist is set to the config element 'record.disableAutoArtist' and DAO is set to a reference to another component's instance. Note that c: and r: can be used as shorthand for config: and ref: See https://granitic.io/ref/component-container for more information
Any error such as type mismatches or missing configuration will cause an error that will halt application startup.
Component templates ¶
A template mechanism exists to allow multiple components that share a type, dependencies or configuration items to only have those elements defined once. This is especially useful for web service handlers. See https://granitic.io/ref/component-templates for more details.
Binding ¶
Unlike JVM/CLR languages, Go has no runtime 'instance-for-type-name' mechanism for creating instances of a struct. As a result, unlike JVM/CLR IoC containers you may have used, the container does not instantiate the actual instances of the Go structs behind application components. Instead a 'binding' process is used - refer to the package documentation for the grnc-bind tool for more information.
Container lifecycle ¶
The process of starting the container transitions through a number of distinct phase. It is possible for your code to be explicitly invoked during some of these phases by implementing one more lifecycle interfaces.
Populate Application and framework components are stored in the container. Configure Configuration and dependencies are resolved and injected into components. Decorate Components implementing the ioc.ComponentDecorator are given access to all other components to potentially modify. Start Components implementing the ioc.Startable interface are invoked. Access Components implementing ioc.AccessibilityBlocker and ioc.Accessible are interacted with. Ready Granitic application is running.
When the container is ready, a log message similar to
grncInit Ready (startup time 6.28ms)
will be logged.
There are several other possible lifecycle phases after the container is ready:
Suspend Components implementing ioc.Suspendable have their Suspend method invoked. Resume Components implementing ioc.Suspendable have their Resume method invoked. Stop Components implementing ioc.Stoppable are allowed to stop gracefully before the application exits.
Decorators ¶
Decorators are special components implementing ioc.ComponentDecorator. Their main purpose is to inject dynamically created objects into other components (such as Loggers). Decorators are destroyed after the Decorate phase of container startup.
Stopping ¶
Components that need to perform some shutdown process before an application exits should implement the Stoppable interface. See the GoDoc for ioc.Stoppable below for more detail.
Container settings ¶
The file $GRANITIC_HOME/resource/facility-config/system.json contains configuration, that can be overridden in your application's configuration file, affecting startup, garbage collection and shutdown behaviour of the container.
More information can be found at https://granitic.io/ref/system-configuration
Gaining access to the container ¶
If your application component needs direct access to the container it should implement the ioc.ContainerAccessor. A reference to the container will be injected into your component during the decorate phase.
External interaction with the container ¶
If your application enables the RuntimeCtl facility, you can interact with the container and its components by using the grnc-ctl command line utility. See the package documentation for grnc-ctl for more information.
Index ¶
- Constants
- type AccessibilityBlocker
- type Accessible
- type ByName
- type Component
- type ComponentContainer
- func (cc *ComponentContainer) AddModifier(comp string, field string, dep string)
- func (cc *ComponentContainer) AddModifiers(mods map[string]map[string]string)
- func (cc *ComponentContainer) AddProto(proto *ProtoComponent)
- func (cc *ComponentContainer) AddProtos(protos []*ProtoComponent)
- func (cc *ComponentContainer) AllComponents() []*Component
- func (cc *ComponentContainer) ByLifecycleSupport(ls LifecycleSupport) []*Component
- func (cc *ComponentContainer) ComponentByName(name string) *Component
- func (cc *ComponentContainer) ModifierExists(comp string, field string) bool
- func (cc *ComponentContainer) Modifiers(comp string) map[string]string
- func (cc *ComponentContainer) ModifiersExist(comp string) bool
- func (cc *ComponentContainer) Populate() error
- func (cc *ComponentContainer) ProtoComponents() map[string]*ProtoComponent
- func (cc *ComponentContainer) ProtoComponentsByType(tm TypeMatcher) []*ProtoComponent
- func (cc *ComponentContainer) WrapAndAddProto(name string, instance interface{})
- type ComponentDecorator
- type ComponentLookup
- type ComponentNamer
- type ComponentState
- type Components
- type ContainerAccessor
- type ContainerDecorator
- type LifecycleManager
- func (lm *LifecycleManager) ResumeComponents(comps []*Component) error
- func (lm *LifecycleManager) Start(startable []*Component) error
- func (lm *LifecycleManager) StartAll() error
- func (lm *LifecycleManager) StopAll() error
- func (lm *LifecycleManager) StopComponents(comps []*Component) error
- func (lm *LifecycleManager) SuspendComponents(comps []*Component) error
- type LifecycleSupport
- type ProtoComponent
- func (pc *ProtoComponent) AddConfigPromise(fieldName, configPath string)
- func (pc *ProtoComponent) AddDefaultValue(fieldName, value string)
- func (pc *ProtoComponent) AddDependency(fieldName, componentName string)
- func (pc *ProtoComponent) DefaultValue(fieldName string) string
- func (pc *ProtoComponent) HasDefaultValue(fieldName string) bool
- type ProtoComponents
- type Startable
- type Stoppable
- type Suspendable
- type TypeMatcher
Constants ¶
const ( //StoppedState indicates that a component has stopped StoppedState = iota //StoppingState indicates that a component is in the process of stopping StoppingState //StartingState indicates that a component is in the process of starting StartingState //AwaitingAccessState indicates that a component is available for connections from external sources AwaitingAccessState //RunningState indicates that a component is running normally RunningState //SuspendingState indicates that a component is in the process of being suspended SuspendingState //SuspendedState indicates that a component has been suspended and is effectively paused SuspendedState //ResumingState indicates that a component in the process of being resumed from a suspended state ResumingState )
const ( // None indicates that the component doesn't support any lifecycle events None = iota // CanStart indicates that the component is Startable CanStart // CanStop indicates that the component is Stoppable CanStop // CanSuspend indicates that the component is Suspendable CanSuspend // CanBlockStart indicates that the component can block the start process if it is not ready CanBlockStart // CanBeAccessed indicates that the component is Accessible CanBeAccessed )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AccessibilityBlocker ¶
type AccessibilityBlocker interface { // BlockAccess returns true if the component wants to prevent the application from becoming ready and accessible. An // optional error message can be returned explaining why. BlockAccess() (bool, error) }
AccessibilityBlocker is implemented by components that MUST be ready before an application can be made accessible. For example, a component connecting to a critical external system might implement AccessibilityBlocker to prevent an HTTP server making an API available until a connection to the critical system is established.
See https://granitic.io/ref/system-configuration for information about the number of times BlockAccess is called, and how the interval between these calls can be adjusted for your application.
type Accessible ¶
type Accessible interface { // AllowAccess is called by the container as the final stage of making an application ready. AllowAccess() error }
Accessible is implemented by components that require a final phase of initialisation to make themselves outside of the application. Typically implemented by HTTP servers and message queue listeners to start listening on TCP ports.
type ByName ¶
type ByName struct{ Components }
ByName allows a slice of components to be sorted by name
type Component ¶
type Component struct { // A pointer to a struct Instance interface{} // A name for this component that is unique within your application Name string }
A Component is an instance of a struct with a name that is unique within your application.
func NewComponent ¶
NewComponent creates a new Component with the supplied name and instance
type ComponentContainer ¶
type ComponentContainer struct { FrameworkLogger logging.Logger Lifecycle *LifecycleManager // contains filtered or unexported fields }
ComponentContainer is the The Granitic IoC container. See the GoDoc for the ioc package for more information on how to interact with the container.
Most applications should never need to interact with the container programmatically.
func NewComponentContainer ¶
func NewComponentContainer(logm *logging.ComponentLoggerManager, ca *config.Accessor, sys *instance.System) *ComponentContainer
NewComponentContainer creates a new instance of a Granitic IoC container.
func (*ComponentContainer) AddModifier ¶
func (cc *ComponentContainer) AddModifier(comp string, field string, dep string)
AddModifier is used to override a dependency on a component (normally a built-in Granitic component) during the configure phase of container startup.
func (*ComponentContainer) AddModifiers ¶
func (cc *ComponentContainer) AddModifiers(mods map[string]map[string]string)
AddModifiers is used to override a dependency on set of components (normally built-in Granitic components) during the configure phase of container startup.
func (*ComponentContainer) AddProto ¶
func (cc *ComponentContainer) AddProto(proto *ProtoComponent)
AddProto registers an instantiated but un-configured proto-component.
func (*ComponentContainer) AddProtos ¶
func (cc *ComponentContainer) AddProtos(protos []*ProtoComponent)
AddProtos registers a collection of proto-components (see AddProto)
func (*ComponentContainer) AllComponents ¶
func (cc *ComponentContainer) AllComponents() []*Component
AllComponents returns all of the components hosted by the container.
func (*ComponentContainer) ByLifecycleSupport ¶
func (cc *ComponentContainer) ByLifecycleSupport(ls LifecycleSupport) []*Component
ByLifecycleSupport returns all components hosted by the container that have specific support for a lifecycle event (i.e. implement the associated lifecycle interface
func (*ComponentContainer) ComponentByName ¶
func (cc *ComponentContainer) ComponentByName(name string) *Component
ComponentByName implements ComponentLookup.ComponentByName
func (*ComponentContainer) ModifierExists ¶
func (cc *ComponentContainer) ModifierExists(comp string, field string) bool
ModifierExists checks to see if a modifier (see AddModifier) has previously been registered for a field on a component.
func (*ComponentContainer) Modifiers ¶
func (cc *ComponentContainer) Modifiers(comp string) map[string]string
Modifiers returns all registered modifiers (see AddModifier)
func (*ComponentContainer) ModifiersExist ¶
func (cc *ComponentContainer) ModifiersExist(comp string) bool
ModifiersExist returns true if any modifiers (see AddModifier) have been registered.
func (*ComponentContainer) Populate ¶
func (cc *ComponentContainer) Populate() error
Populate converts all registered proto-components into components and populates them with configuration and dependencies.
func (*ComponentContainer) ProtoComponents ¶
func (cc *ComponentContainer) ProtoComponents() map[string]*ProtoComponent
ProtoComponents returns all components that have been registered by the container but have not yet all their dependencies resolved. If called after the container is 'Accessible' an empty slice will be returned.
func (*ComponentContainer) ProtoComponentsByType ¶
func (cc *ComponentContainer) ProtoComponentsByType(tm TypeMatcher) []*ProtoComponent
ProtoComponentsByType returns any ProtoComponents whose Component.Instance field matches the against the supplied TypeMatcher function. If called after the container is 'Accessible' an empty slice will be returned.
func (*ComponentContainer) WrapAndAddProto ¶
func (cc *ComponentContainer) WrapAndAddProto(name string, instance interface{})
WrapAndAddProto registers an instance and name as an un-configured proto-component.
type ComponentDecorator ¶
type ComponentDecorator interface { // OfInterest should return true if the ComponentDecorator decides that the supplied component needs to be decorated. OfInterest(subject *Component) bool // DecorateComponent modifies the subject component. DecorateComponent(subject *Component, container *ComponentContainer) }
ComponentDecorator is implemented by special temporary components that only exist while the IoC container is being populated.
A ComponentDecorator's job is to examine another component (the subject) to see if it is suitable for modification by the ComponentDecorator. Typically this will involve the ComponentDecorator injecting another object into the subject if the component implements a particular interface or has a writable field of a particular name or type. A number of built-in decorators exist to accomplish tasks like automatically adding Loggers to components with a particular field.
type ComponentLookup ¶
type ComponentLookup interface { // ComponentByName returns the Component with the supplied name, or nil if it does not exist. ComponentByName(string) *Component // AllComponents returns all registered components AllComponents() []*Component }
ComponentLookup is used by components that have visibility of other components stored in the IOC container
type ComponentNamer ¶
type ComponentNamer interface { // ComponentName returns the name of the component ComponentName() string // SetComponentName injects the component's name SetComponentName(name string) }
ComponentNamer is implemented by components where the component's instance needs to be aware of its own component name.
type ComponentState ¶
type ComponentState int
ComponentState represents what state (stopped, running) or transition between states (stopping, starting) a component is currently in.
type Components ¶
type Components []*Component
Components is a type definition for a slice of components to allow sorting.
func (Components) Len ¶
func (s Components) Len() int
Len returns the number of components in the slice
func (Components) Swap ¶
func (s Components) Swap(i, j int)
Swap exchanges the position of the components at the specified indexes
type ContainerAccessor ¶
type ContainerAccessor interface { // Container accepts a reference to the Granitic IoC container. Container(container *ComponentContainer) }
ContainerAccessor is implemented by any component that wants direct access to the IoC container.
type ContainerDecorator ¶
type ContainerDecorator struct {
// contains filtered or unexported fields
}
ContainerDecorator injects a reference to the IoC container into any component implementing the ContainerAccessor interface.
func (*ContainerDecorator) DecorateComponent ¶
func (cd *ContainerDecorator) DecorateComponent(subject *Component, cc *ComponentContainer)
DecorateComponent injects a reference to the IoC container to a component that has alredy been determined to implement ContainerAccessor.
func (*ContainerDecorator) OfInterest ¶
func (cd *ContainerDecorator) OfInterest(subject *Component) bool
OfInterest returns true if the subject component implements ContainerAccessor
type LifecycleManager ¶
type LifecycleManager struct { FrameworkLogger logging.Logger // contains filtered or unexported fields }
LifecycleManager provides an interface to the components to allow lifecycle methods/events to be applied to all components or a subset of the components.
func (*LifecycleManager) ResumeComponents ¶
func (lm *LifecycleManager) ResumeComponents(comps []*Component) error
ResumeComponents invokes Resume on all of the supplied components that implement Suspendable
func (*LifecycleManager) Start ¶
func (lm *LifecycleManager) Start(startable []*Component) error
Start starts the supplied components, waits for any access-blocking components to be ready, then makes all components accessible. See GoDoc for Startable, AccessibilityBlocker and Accessible for more details.
func (*LifecycleManager) StartAll ¶
func (lm *LifecycleManager) StartAll() error
StartAll finds all Startable and Accessible components runs the Start/Block/Accessible cycle.
func (*LifecycleManager) StopAll ¶
func (lm *LifecycleManager) StopAll() error
StopAll finds all components implementing Stoppable and passes them to Stop
func (*LifecycleManager) StopComponents ¶
func (lm *LifecycleManager) StopComponents(comps []*Component) error
StopComponents invokes PrepareToStop on all components then waits for them to be ready to stop by calling ReadyToStop on each component. If one or more components are not ready, they are given x chances to become ready with y milliseconds between each check. See https://granitic.io/ref/system-configuration
If all components are ready, or if x has been exceeded, Stop is called on all components.
func (*LifecycleManager) SuspendComponents ¶
func (lm *LifecycleManager) SuspendComponents(comps []*Component) error
SuspendComponents invokes Suspend on all of the supplied components that implement Suspendable
type LifecycleSupport ¶
type LifecycleSupport int
LifecycleSupport is an enumeration able used to categorise types by the lifecycle events they can react to.
type ProtoComponent ¶
type ProtoComponent struct { // The name of a component and the component instance (a pointer to an instantiated struct). Component *Component // A map of fields on the component instance and the names of other components that should be injected into those fields. Dependencies map[string]string // A map of fields on the component instance and the config-path that will contain the configuration that shoud be inject into the field. ConfigPromises map[string]string // A map of default values for fields if a config promise is not fulfilled DefaultValues map[string]string }
A ProtoComponent is a partially configured component that will be hosted in the Granitic IoC container once it is fully configured. Typically ProtoComponents are created using the grnc-bind tool.
func CreateProtoComponent ¶
func CreateProtoComponent(componentInstance interface{}, componentName string) *ProtoComponent
CreateProtoComponent creates a new ProtoComponent.
func (*ProtoComponent) AddConfigPromise ¶
func (pc *ProtoComponent) AddConfigPromise(fieldName, configPath string)
AddConfigPromise requests that the container injects the config value at the specified path into the specified field during the configure phase of container startup.
func (*ProtoComponent) AddDefaultValue ¶
func (pc *ProtoComponent) AddDefaultValue(fieldName, value string)
AddDefaultValue records an untyped default value to use if a config promise is not fulfilled
func (*ProtoComponent) AddDependency ¶
func (pc *ProtoComponent) AddDependency(fieldName, componentName string)
AddDependency requests that the container injects another component into the specified field during the configure phase of container startup
func (*ProtoComponent) DefaultValue ¶
func (pc *ProtoComponent) DefaultValue(fieldName string) string
DefaultValue returns the default value recorded for the supplied field or empty string if it hasn't been set
func (*ProtoComponent) HasDefaultValue ¶
func (pc *ProtoComponent) HasDefaultValue(fieldName string) bool
HasDefaultValue returns true if a default value has been registered for the supplied field
type ProtoComponents ¶
type ProtoComponents struct { // ProtoComponents to be finalised and stored in the IoC container. Components []*ProtoComponent // FrameworkDependencies are instructions to inject components into built-in Granitic components to alter their behaviour. // The structure is map[ FrameworkDependencies map[string]map[string]string //A Base64 encoded version of the JSON files found in resource/facility-confg FrameworkConfig *string }
ProtoComponents is a wrapping structure for a list of ProtoComponents and FrameworkDependencies that is required when starting Granitic. A ProtoComponents structure is built by the grnc-bind tool.
func NewProtoComponents ¶
func NewProtoComponents(pc []*ProtoComponent, fd map[string]map[string]string, ser *string) *ProtoComponents
NewProtoComponents creates a wrapping structure for a list of ProtoComponents
func (*ProtoComponents) Clear ¶
func (pc *ProtoComponents) Clear()
Clear removes the reference to the ProtoComponent objects held in this object, encouraging garbage collection.
type Startable ¶
type Startable interface { // StartComponent performs initialisation and may start listeners/servers. StartComponent() error }
Startable is implemented by components that need to perform some initialisation before they are ready to run or use.
Components that provide services outside of the application (like HTTP servers or queue listeners) should consider implementing Accessible in addition to Startable.
type Stoppable ¶
type Stoppable interface { // PrepareToStop gives notice to the component that the application is about to halt. The implementation of this method // should cause the component to block any new requests for work. PrepareToStop() // ReadyToStop is called by the container to query whether or not the component is ready to shutdown. A component might // return false if it is still processing or running a job. If the component returns false, it may optionally return // an error to explain why it is not ready. ReadyToStop() (bool, error) // Stop is an instruction by the container to immediately terminate any running processes and release any resources. // If the component is unable to do so, it may return an error, but the application will still stop. Stop() error }
Stoppable is implemented by components that need to be given the opportunity to release resources or perform shutdown activities before an application is halted.
See https://granitic.io/ref/system-configuration for information about the number of times ReadyToStop is called, and how the interval between these calls, can be adjusted for your application.
type Suspendable ¶
type Suspendable interface { // Suspend causes the component to stop performing its primary function until Resume is called. Suspend() error // Resume causes the component to resume its primary function. Resume() error }
Suspendable is implemented by components that are able to temporarily halt and then resume their activity at some later point in time.
type TypeMatcher ¶
type TypeMatcher func(i interface{}) bool
TypeMatcher implementations return true if the supplied interface is (or implements) an expected type