Skip to content

Commit 5bd6271

Browse files
committed
Update documentation for contexts
1 parent 243a01a commit 5bd6271

File tree

7 files changed

+249
-8
lines changed

7 files changed

+249
-8
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ In this section, we will try to cover DataForge main ideas in the form of questi
5050

5151
* **Modularisation**. Contrary to lot other frameworks, DataForge is intrinsically modular. The mandatory part is a rather tiny core module. Everything else could be customized.
5252

53-
* **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world.
53+
* **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world. See [Context](../dataforge-context/docs/context.md) and [Plugin Mechanics](../dataforge-context/docs/plugins.md) for more details.
5454

5555
### Misc
5656

@@ -71,6 +71,12 @@ In this section, we will try to cover DataForge main ideas in the form of questi
7171
> Context and provider definitions
7272
>
7373
> **Maturity**: DEVELOPMENT
74+
>
75+
> **Features:**
76+
> - [context](dataforge-context/docs/context.md) : Context is a single extension point for all applications. It allows to configure application and handle its lifecycle.
77+
> - [plugins](dataforge-context/docs/plugins.md) : Plugin system is a powerful dependency injection and extensibility mechanism.
78+
> - [context-serialization](dataforge-context/docs/context-serialization.md) : Serializer aggregation for context. Each plugin could supply its own serializer module.
79+
7480

7581
### [dataforge-data](dataforge-data)
7682
>

dataforge-context/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
Context and provider definitions
44

5+
## Features
6+
7+
- [context](docs/context.md) : Context is a single extension point for all applications. It allows to configure application and handle its lifecycle.
8+
- [plugins](docs/plugins.md) : Plugin system is a powerful dependency injection and extensibility mechanism.
9+
- [context-serialization](docs/context-serialization.md) : Serializer aggregation for context. Each plugin could supply its own serializer module.
10+
11+
512
## Usage
613

714
## Artifact:
815

9-
The Maven coordinates of this project are `space.kscience:dataforge-context:0.11.0-dev-1`.
16+
The Maven coordinates of this project are `space.kscience:dataforge-context:0.11.0-dev-2`.
1017

1118
**Gradle Kotlin DSL:**
1219
```kotlin
@@ -16,6 +23,6 @@ repositories {
1623
}
1724

1825
dependencies {
19-
implementation("space.kscience:dataforge-context:0.11.0-dev-1")
26+
implementation("space.kscience:dataforge-context:0.11.0-dev-2")
2027
}
2128
```

dataforge-context/build.gradle.kts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,22 @@ kscience {
2525
readme {
2626
maturity = space.kscience.gradle.Maturity.DEVELOPMENT
2727

28-
feature("context-serialization","src/docs/context-serialization.md"){
28+
feature("context", ref = "docs/context.md"){
29+
"""
30+
Context is a single extension point for all applications. It allows to configure application and handle its lifecycle.
31+
""".trimIndent()
32+
}
33+
34+
feature("plugins", ref = "docs/plugins.md"){
35+
"""
36+
Plugin system is a powerful dependency injection and extensibility mechanism.
37+
It allows to construct required capabilities for specific application in a single space.
38+
Also it provides a capability sharing bus. For example one plugin could provide factories for dynamic
39+
instantiation in another plugin.
40+
""".trimIndent()
41+
}
42+
43+
feature("context-serialization","docs/context-serialization.md"){
2944
"""
3045
Serializer aggregation for context. Each plugin could supply its own serializer module.
3146
Those modules are aggregated into one serializer module in context.

dataforge-context/docs/context.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Context
2+
3+
Context is a central point in dependency injection and lifecycle management. Its extensibility is based on [plugins](plugins.md). It also serves as a `CoroutineScope` and manages lifecycle of the application. Closing a component context terminates it.
4+
5+
## Global Context
6+
7+
`Global` is a special context that is always available. It should not be used outside of tests. Similar to `GlobalScope` in coroutines, it deos not provide proper lifecycle management. `Global` could be closed, but it usually means termination of whole the application.
8+
9+
## Building a Context
10+
11+
In DataForge, the `Context` represents the environment where operations are executed. It manages plugins, properties, and coroutines. Contexts are organized in a hierarchy, with `Global` at the root.
12+
13+
### Basic Context Creation
14+
15+
The simplest way to create a context is to use the `Context` factory function:
16+
17+
```kotlin
18+
val myContext = Context("my-context")
19+
```
20+
21+
This creates a new child context of the `Global` context with the name `my-context`. If the context with the given name already exists, throw an exception.
22+
23+
### The Context Builder
24+
25+
For more complex configurations, you can use the `ContextBuilder`. This is available through the `Context` factory function or by calling `buildContext` on an existing context.
26+
27+
```kotlin
28+
val myContext = Context("my-context") {
29+
// Set a property
30+
properties {
31+
"myProperty" put 42
32+
}
33+
34+
// Add a plugin by its factory
35+
plugin(MyPluginFactory) {
36+
// Plugin-specific configuration
37+
"pluginConfig" put "someValue"
38+
}
39+
40+
// Configure the coroutine context
41+
coroutineContext(Dispatchers.Default)
42+
}
43+
```
44+
45+
### Properties
46+
47+
Properties in a context work like environment variables. They are inherited from the parent context. In the builder, you can set them using the `properties` block:
48+
49+
```kotlin
50+
properties {
51+
"node" put {
52+
"value" put "something"
53+
}
54+
}
55+
```
56+
57+
### Plugins
58+
59+
Plugins provide the actual functionality to the context. You can add plugins to a context during construction:
60+
61+
- **By Factory**: The most common way. Allows providing configuration metadata.
62+
```kotlin
63+
plugin(MyPluginFactory) {
64+
"key" put "value"
65+
}
66+
```
67+
- **By Tag**: If the factory is available in the parent context's registry.
68+
```kotlin
69+
plugin("my-plugin", group = "my-group")
70+
```
71+
- **Existing Instance**: Add an already created plugin instance.
72+
```kotlin
73+
plugin(myPluginInstance)
74+
```
75+
76+
### Coroutine Context
77+
78+
Each `Context` is a `CoroutineScope`. You can customize its `CoroutineContext` during building:
79+
80+
```kotlin
81+
coroutineContext(SupervisorJob() + Dispatchers.IO)
82+
```
83+
84+
By default, a new context inherits the coroutine context from its parent and adds a `SupervisorJob`.
85+
86+
## Child Contexts
87+
88+
You can create child contexts from any existing context:
89+
90+
```kotlin
91+
val child = parentContext.buildContext("child") {
92+
// ...
93+
}
94+
```
95+
96+
Child context inherits all properties and plugins from their parent but could override them. For example, if a child provides a plugin with the same tag as a parent, a child plugin is used. In some cases child plugins could use some information from parent plugins. Parent context usually does not use child plugin information.
97+
98+
Closing parent context automatically closes all its child contexts.
99+
100+
If a child context with the same name already exists, `buildContext` will return it (potentially deriving it if additional configuration is provided).
101+
102+
## Example: Complex Context Setup
103+
104+
```kotlin
105+
val analysisContext = Context("analysis") {
106+
properties {
107+
"threads" put 4
108+
}
109+
110+
plugin(LogManager)
111+
112+
plugin(StoragePlugin) {
113+
"path" put "./data"
114+
}
115+
}
116+
117+
val subTaskContext = analysisContext.buildContext("subtask") {
118+
properties {
119+
"priority" put "high"
120+
}
121+
}
122+
```
123+
124+
In this example, `subTaskContext` will have access to the `LogManager` and `StoragePlugin` from its parent, as well as the `threads` property.

dataforge-context/docs/plugins.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# DataForge Plugin Mechanics
2+
3+
Plugins are the primary way to extend the functionality of a DataForge `Context`. They provide runtime features, services, and configurations that can be shared across different tasks executed within a context.
4+
5+
## The Plugin Interface
6+
7+
A plugin in DataForge is defined by the `Plugin` interface. Every plugin must have:
8+
- A `PluginTag`: Contains the `name`, `group`, and `version` of the plugin.
9+
- A `Meta` configuration: Allows the plugin to be configured.
10+
- A reference to the `Context` it is attached to (via `ContextAware`).
11+
12+
```kotlin
13+
public interface Plugin : Named, ContextAware, Provider, MetaRepr {
14+
public val tag: PluginTag
15+
public val meta: Meta
16+
public fun dependsOn(): Map<PluginFactory<*>, Meta>
17+
public fun attach(context: Context)
18+
public fun detach()
19+
public val isAttached: Boolean
20+
// ...
21+
}
22+
```
23+
24+
## Plugin Lifecycle
25+
26+
The lifecycle of a plugin consists of the following stages:
27+
28+
1. **Create**: The plugin instance is created and configured, usually via a `PluginFactory`.
29+
2. **Attach**: The plugin is attached to a `Context`. This is when the plugin can register its services in the context and initialize itself. The `attach(context)` method is called by the `PluginManager`.
30+
3. **Detach**: The plugin is removed from the `Context`. It should clean up any resources and registrations.
31+
4. **Destroy**: The plugin instance is discarded.
32+
33+
## Plugin Identification (PluginTag)
34+
35+
Plugins are identified by a `PluginTag`, which consists of:
36+
- `name`: The unique name of the plugin within its group.
37+
- `group`: (Optional) The organization or project the plugin belongs to.
38+
- `version`: (Optional) The version of the plugin. Version could be used for remote plugin management and for consistent result storage.
39+
40+
The `PluginManager` uses these tags to find and manage plugins, ensuring that no two plugins with the same tag are loaded into the same context.
41+
42+
## Dependency Management
43+
44+
Plugins can declare dependencies on other plugins. Before a plugin is attached, all its dependencies must be present and attached to the context (or its parents).
45+
46+
### Declaring Dependencies
47+
In `AbstractPlugin`, dependencies are declared using the `require` function:
48+
49+
```kotlin
50+
class MyPlugin(meta: Meta) : AbstractPlugin(meta) {
51+
// Declares a dependency on AnotherPlugin
52+
val anotherPlugin by require(AnotherPluginFactory)
53+
}
54+
```
55+
56+
The `require` function returns a delegate that lazily fetches the dependent plugin from the context once the plugin is attached.
57+
58+
## Plugin Manager
59+
60+
Each `Context` has a `PluginManager` that:
61+
- Stores attached plugins.
62+
- Manages the plugin lifecycle.
63+
- Handles plugin lookups (including searching in parent contexts).
64+
65+
### Requesting a Plugin
66+
You can request a plugin from a context using the `request` extension function:
67+
68+
```kotlin
69+
val myPlugin = context.request(MyPluginFactory)
70+
```
71+
72+
If the plugin is already present in the context or its parents, it is returned. Otherwise, a new child context is created with the requested plugin.
73+
74+
## Plugin Factories
75+
76+
Plugins are typically created using a `PluginFactory`. This allows the framework to instantiate plugins dynamically based on metadata or explicit requests. Also it allows using type-safe reference to plugins via their types and dependency injection.
77+
78+
```kotlin
79+
public interface PluginFactory<T : Plugin> : Factory<T> {
80+
public val tag: PluginTag
81+
override fun build(context: Context, meta: Meta): T
82+
}
83+
```

dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,15 @@ public open class Context internal constructor(
8181
private val childrenContexts = HashMap<Name, Context>()
8282

8383
/**
84-
* Get and validate existing context or build and register a new child context.
85-
* @param name the relative (tail) name of the new context. If null, uses context hash code as a marker.
84+
* Build and register a new child context.
85+
* @param name the relative (tail) name of the new context. If null, use context hash code as a marker.
8686
*/
8787
@OptIn(DFExperimental::class)
8888
@ThreadSafe
8989
public fun buildContext(name: Name? = null, block: ContextBuilder.() -> Unit = {}): Context {
9090
val existing = name?.let { childrenContexts[name] }
91-
return existing?.deriveContext(block = block) ?: ContextBuilder(this, name).apply(block).build().also {
91+
if (existing != null) error("Context $name already exists in $this")
92+
return ContextBuilder(this, name).apply(block).build().also {
9293
childrenContexts[it.name] = it
9394
}
9495
}
@@ -109,6 +110,11 @@ public open class Context internal constructor(
109110
"plugins" putIndexed plugins.map { it.toMeta() }
110111
}
111112

113+
override fun toString(): String {
114+
val parentString = if(parent == Global) "" else ", parent=$parent"
115+
return "Context(name=$name$parentString)"
116+
}
117+
112118
/**
113119
* A lazily initialized property that defines the serialization module for the current context.
114120
*

docs/README-TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ In this section, we will try to cover DataForge main ideas in the form of questi
5050

5151
* **Modularisation**. Contrary to lot other frameworks, DataForge is intrinsically modular. The mandatory part is a rather tiny core module. Everything else could be customized.
5252

53-
* **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world.
53+
* **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world. See [Context](../dataforge-context/docs/context.md) and [Plugin Mechanics](../dataforge-context/docs/plugins.md) for more details.
5454

5555
### Misc
5656

0 commit comments

Comments
 (0)