Module tvix_castore::composition

source ·
Expand description

The composition module allows composing different kinds of services based on a set of service configurations at runtime.

Store configs are deserialized with serde. The registry provides a stateful mapping from the type tag of an internally tagged enum on the serde side to a Config struct which is deserialized and then returned as a Box<dyn ServiceBuilder<Output = dyn BlobService>> (the same for DirectoryService instead of BlobService etc).

§Example 1.: Implementing a new BlobService

You need a Config struct which implements DeserializeOwned and ServiceBuilder<Output = dyn BlobService>. Provide the user with a function to call with their registry. You register your new type as:

use std::sync::Arc;

use tvix_castore::composition::*;
use tvix_castore::blobservice::BlobService;

#[derive(serde::Deserialize)]
struct MyBlobServiceConfig {
}

#[tonic::async_trait]
impl ServiceBuilder for MyBlobServiceConfig {
    type Output = dyn BlobService;
    async fn build(&self, _: &str, _: &CompositionContext) -> Result<Arc<Self::Output>, Box<dyn std::error::Error + Send + Sync + 'static>> {
        todo!()
    }
}

impl TryFrom<url::Url> for MyBlobServiceConfig {
    type Error = Box<dyn std::error::Error + Send + Sync>;
    fn try_from(url: url::Url) -> Result<Self, Self::Error> {
        todo!()
    }
}

pub fn add_my_service(reg: &mut Registry) {
    reg.register::<Box<dyn ServiceBuilder<Output = dyn BlobService>>, MyBlobServiceConfig>("myblobservicetype");
}

Now, when a user deserializes a store config with the type tag “myblobservicetype” into a Box<dyn ServiceBuilder<Output = Arc<dyn BlobService>>>, it will be done via MyBlobServiceConfig.

§Example 2.: Composing stores to get one store

use std::sync::Arc;
use tvix_castore::composition::*;
use tvix_castore::blobservice::BlobService;

let blob_services_configs_json = serde_json::json!({
  "blobstore1": {
    "type": "memory"
  },
  "blobstore2": {
    "type": "memory"
  },
  "default": {
    "type": "combined",
    "local": "blobstore1",
    "remote": "blobstore2"
  }
});

let blob_services_configs = with_registry(&REG, || serde_json::from_value(blob_services_configs_json))?;
let mut blob_service_composition = Composition::new(&REG);
blob_service_composition.extend_with_configs::<dyn BlobService>(blob_services_configs);
let blob_service: Arc<dyn BlobService> = blob_service_composition.build("default").await?;

§Example 3.: Creating another registry extending the default registry with third-party types

let mut my_registry = tvix_castore::composition::Registry::default();
tvix_castore::composition::add_default_services(&mut my_registry);
add_my_service(&mut my_registry);

Continue with Example 2, with my_registry instead of REG

EXPERIMENTAL: If the xp-store-composition feature is enabled, entrypoints can also be URL strings, which are created as anonymous stores. Instantiations of the same URL will result in a new, distinct anonymous store each time, so creating two memory:// stores with this method will not share the same view. This behavior might change in the future.

Structs§

Enums§

Constants§

  • ACTIVE_REG 🔒
    The active Registry is global state, because there is no convenient and universal way to pass state into the functions usually used for deserialization, e.g. serde_json::from_str, toml::from_str, serde_qs::from_str.

Statics§

  • The provided registry of tvix_castore, with all builtin BlobStore/DirectoryStore implementations

Traits§

  • This is the trait usually implemented on a per-store-type Config struct and used to instantiate it.

Functions§

  • Register the builtin services of tvix_castore with the given registry. This is useful for creating your own registry with the builtin types and extra third party types.
  • Run the provided closure with a registry context. Any serde deserialize calls within the closure will use the registry to resolve tag names to the corresponding Config type.

Type Aliases§