Package io.nosqlbench.nb.api.components
The component types in this package create a basic structural and lifecycle pattern that NoSQLBench runtimes should build upon. This allows leverage of the base component types to achieve:
- consistent runtime behavior between modules
- modular composition of runtime modules in different configurations
- a uniform configuration interface across all components
- exposition of foundational runtime structure to users
- consistent layering of components and their lifetimes at different runtime scopes
- a naming and metadata based view which allows for components to be managed in a tangible way
- a consistent manifest and registry interface for attached elements, like metrics
Components Defined
There can be different layers or types of components, but there is only one component hierarchy in each NoSQLBench process. Basically the Session is a component. Each activity container within a session is a component. Each activity within a container is a component. Whether something is meaningful as a component depends on whether the management facilities provided by the component API make using, interacting with, or understanding that element better for users. However there is a limit to how fine-grained the component hierarchy should be allowed to get. This is because maintaining the component structure at runtime incurs a cost, and most features of the component types are related to assemblage of fixtures in the runtime which are configured and initialized before steady state processing begins. For example, it makes sense to wire an activity as component, but not an operation, since an operation is ephemeral and short lived. Apart from these trade-offs, make a layer a component layer if it makes sense for the user and/or the developer, as consolidating the logic into the component layer is beneficial to both.
Adoption Strategy
Consolidating existing logic to use the component types will be an incremental process. The base contract type
NBComponent establishes the contract for any conforming types. As
contract facets are added to this type, common logic can be implemented on the base implementation types where
possible, allowing for the elision of duplicitous code from prior functionality.
Component System Design
All key types in the NBComponent system must have names starting with NBComponent.
Components are structured hierarchically. All components exist within the scope of their parent, with the only
exception being the root component, which has no parent. Components always know their parent from construction time.
After a component is constructed, it is informed of child components being added and removed via
NBComponent.attachChild(io.nosqlbench.nb.api.components.core.NBComponent...) and
NBComponent.detachChild(io.nosqlbench.nb.api.components.core.NBComponent...)
methods.
Component-specific logic should interact with other components using the component interfaces and types. No contextual typing or casting should be allowed within the component layer methods, except in the case that types outside of the component type system are leveraging component layers for features of other non-component types. In other words, Component APIs should be thought of as a layer of primitives which sit on top of low-level Java primitive, and below those types which use them. Component logic can't understand higher types without breaking the seal on a useful abstraction. Components only understand components by design, and breaking this abstraction is counter-productive at best.
Utility classes which understand how to interact with components should be used where possible when the capabilities they provide are well-themed and cohesive. A Corollary to this is that each interface added to the core component type should be solely informational about the structure and properties of the component hierarchy when possible. This will help organize usage patterns around themed utilities and keep the surface area of the core types to a minimum.
Types of Components
The purpose of a component can determine its style of implementation.Life-cycle Oriented Components
Life-cycle components represent executions of some user-managed scope, like session, scenarios, or activities. These components model the nesting structure of threads of execution, or for activities, groups of threads. As such, any sub-components they have are generally there to augment or contextualize the execution of the life-cycle component.
Life-cycle components may control the life-line of its sub-components. In that case, when the life-cycle component is ready to finish its own execution, it should directly inform all components attached to it that it is time for them to do final housekeeping, including any final buffering and sending, connection tear-down, etc. As components, the parent component may not know what the details of these housekeeping steps are directly. But components are always something else too, and in the type-specific implementations which are triggered by component methods, appropriate integrations can take place indirectly through the component API or events.
In other life-cycle components, the parent component can be thought of as a passive container over child components which may exit on their own terms. In this case, the parent component should have appropriate eventing and state tracking so that its life-cycle is properly bounded around the sub components. In other words, it would be a bug if all the sub components had completed their work and the parent component sat idle in the component hierarchy without completing itself or alternately sending events to trigger some other life-cycle cleanup. How this is handled is not specific explicitly for all components. Still, it is a design error to have idle components in the hierarchy with no durable life-cycle cleanup mechanism.
Service Oriented Components
Service components are those which are created as an attachment to other components. They may or may not have additional asynchronous behavior with respect to their parent component, but they are always in service of the parent component. For example, metrics instruments like counters are passive, but reporters which send these metrics outside of the system are active and on a schedule. Service-oriented components generally do not control the duration of their lifetimes. When they are working properly, they exist along-side and attached to their parent component for the full lifetime of that parent, and then are reaped on demand by the parent component.
Ideally reaping of sub-components like this occurs naturally because of standard GC reachability rules. For this to work reliably, it is important that you attach components and their children correctly with the component tree, and avoid creating extraneous references across unrelated components or across component layers.