Skip to content

Commit

Permalink
Merge pull request #2687 from fermyon/non-generic-key-value-store
Browse files Browse the repository at this point in the history
Non generic `KeyValueFactor`
  • Loading branch information
rylev authored Jul 30, 2024
2 parents bb35e84 + afebc8e commit 3c087c3
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 231 deletions.
2 changes: 1 addition & 1 deletion crates/factor-key-value-azure/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::Deserialize;
use spin_factor_key_value::MakeKeyValueStore;
use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore;
use spin_key_value_azure::{
KeyValueAzureCosmos, KeyValueAzureCosmosAuthOptions, KeyValueAzureCosmosRuntimeConfigOptions,
};
Expand Down
2 changes: 1 addition & 1 deletion crates/factor-key-value-redis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::Deserialize;
use spin_factor_key_value::MakeKeyValueStore;
use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore;
use spin_key_value_redis::KeyValueRedis;

/// A key-value store that uses Redis as the backend.
Expand Down
6 changes: 3 additions & 3 deletions crates/factor-key-value-spin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use anyhow::Context;
use serde::{Deserialize, Serialize};
use spin_factor_key_value::MakeKeyValueStore;
use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore;
use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite};

/// A key-value store that uses SQLite as the backend.
Expand All @@ -32,10 +32,10 @@ impl SpinKeyValueRuntimeConfig {
/// The default filename for the SQLite database.
const DEFAULT_SPIN_STORE_FILENAME: &'static str = "sqlite_key_value.db";

/// Create a new runtime configuration with the given state directory.
/// Create a new runtime configuration with the given directory.
///
/// If the database directory is None, the database is in-memory.
/// If the database directory is Some, the database is stored in a file in the state directory.
/// If the database directory is Some, the database is stored in a file in the given directory.
pub fn default(default_database_dir: Option<PathBuf>) -> Self {
let path = default_database_dir.map(|dir| dir.join(Self::DEFAULT_SPIN_STORE_FILENAME));
Self { path }
Expand Down
86 changes: 0 additions & 86 deletions crates/factor-key-value/src/delegating_resolver.rs

This file was deleted.

62 changes: 30 additions & 32 deletions crates/factor-key-value/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
pub mod delegating_resolver;
mod runtime_config;
mod store;
pub use delegating_resolver::{DelegatingRuntimeConfigResolver, StoreConfig};
pub mod runtime_config;

use std::{
collections::{HashMap, HashSet},
sync::Arc,
};

use anyhow::ensure;
use runtime_config::{RuntimeConfig, RuntimeConfigResolver};
use spin_factors::{
ConfigureAppContext, Factor, FactorInstanceBuilder, InitContext, InstanceBuilders,
PrepareContext, RuntimeFactors,
Expand All @@ -18,27 +14,28 @@ use spin_key_value::{
CachingStoreManager, DefaultManagerGetter, DelegatingStoreManager, KeyValueDispatch,
StoreManager, KEY_VALUE_STORES_KEY,
};
pub use store::MakeKeyValueStore;

pub use runtime_config::RuntimeConfig;

/// A factor that provides key-value storage.
pub struct KeyValueFactor<R> {
/// Resolves runtime configuration into store managers.
runtime_config_resolver: Arc<R>,
pub struct KeyValueFactor {
default_label_resolver: Arc<dyn DefaultLabelResolver>,
}

impl<R> KeyValueFactor<R> {
impl KeyValueFactor {
/// Create a new KeyValueFactor.
///
/// The `runtime_config_resolver` is used to resolve runtime configuration into store managers.
pub fn new(runtime_config_resolver: R) -> Self {
/// The `default_label_resolver` is used to resolve store managers for labels that
/// are not defined in the runtime configuration.
pub fn new(default_label_resolver: impl DefaultLabelResolver + 'static) -> Self {
Self {
runtime_config_resolver: Arc::new(runtime_config_resolver),
default_label_resolver: Arc::new(default_label_resolver),
}
}
}

impl<R: RuntimeConfigResolver + 'static> Factor for KeyValueFactor<R> {
type RuntimeConfig = RuntimeConfig<R::Config>;
impl Factor for KeyValueFactor {
type RuntimeConfig = RuntimeConfig;
type AppState = AppState;
type InstanceBuilder = InstanceBuilder;

Expand All @@ -52,23 +49,10 @@ impl<R: RuntimeConfigResolver + 'static> Factor for KeyValueFactor<R> {
&self,
mut ctx: ConfigureAppContext<T, Self>,
) -> anyhow::Result<Self::AppState> {
// Build store manager from runtime config
let mut store_managers: HashMap<String, Arc<dyn StoreManager>> = HashMap::new();
if let Some(runtime_config) = ctx.take_runtime_config() {
for (store_label, config) in runtime_config.store_configs {
if let std::collections::hash_map::Entry::Vacant(e) =
store_managers.entry(store_label)
{
// Only add manager for labels that are not already configured. Runtime config
// takes top-down precedence.
let store = self.runtime_config_resolver.get_store(config)?;
e.insert(store);
}
}
}
let resolver_clone = self.runtime_config_resolver.clone();
let store_managers = ctx.take_runtime_config().unwrap_or_default();
let default_label_resolver = self.default_label_resolver.clone();
let default_fn: DefaultManagerGetter =
Arc::new(move |label| resolver_clone.default_store(label));
Arc::new(move |label| default_label_resolver.default(label));

let delegating_manager = DelegatingStoreManager::new(store_managers, default_fn);
let caching_manager = CachingStoreManager::new(delegating_manager);
Expand All @@ -87,7 +71,7 @@ impl<R: RuntimeConfigResolver + 'static> Factor for KeyValueFactor<R> {
// TODO: port nicer errors from KeyValueComponent (via error type?)
ensure!(
store_manager_manager.is_defined(label)
|| self.runtime_config_resolver.default_store(label).is_some(),
|| self.default_label_resolver.default(label).is_some(),
"unknown key_value_stores label {label:?} for component {component_id:?}"
);
}
Expand Down Expand Up @@ -159,3 +143,17 @@ impl FactorInstanceBuilder for InstanceBuilder {
Ok(dispatch)
}
}

/// Resolves a label to a default [`StoreManager`].
pub trait DefaultLabelResolver: Send + Sync {
/// If there is no runtime configuration for a given store label, return a default store manager.
///
/// If `Option::None` is returned, the store is not allowed.
fn default(&self, label: &str) -> Option<Arc<dyn StoreManager>>;
}

impl<T: DefaultLabelResolver> DefaultLabelResolver for Arc<T> {
fn default(&self, label: &str) -> Option<Arc<dyn StoreManager>> {
self.as_ref().default(label)
}
}
36 changes: 18 additions & 18 deletions crates/factor-key-value/src/runtime_config.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
pub mod spin;

use std::{collections::HashMap, sync::Arc};

use serde::{de::DeserializeOwned, Deserialize};
use spin_factors::anyhow;
use spin_key_value::StoreManager;

/// Runtime configuration for all key value stores.
#[derive(Deserialize)]
#[serde(transparent)]
pub struct RuntimeConfig<C> {
/// Map of store names to store configurations.
pub store_configs: HashMap<String, C>,
#[derive(Default)]
pub struct RuntimeConfig {
/// Map of store names to store managers.
store_managers: HashMap<String, Arc<dyn StoreManager>>,
}

/// Resolves some piece of runtime configuration to a key value store manager.
pub trait RuntimeConfigResolver: Send + Sync {
/// The type of configuration that this resolver can handle.
type Config: DeserializeOwned;
impl RuntimeConfig {
/// Adds a store manager for the store with the given label to the runtime configuration.
pub fn add_store_manager(&mut self, label: String, store_manager: Arc<dyn StoreManager>) {
self.store_managers.insert(label, store_manager);
}
}

/// Get a store manager for a given config.
fn get_store(&self, config: Self::Config) -> anyhow::Result<Arc<dyn StoreManager>>;
impl IntoIterator for RuntimeConfig {
type Item = (String, Arc<dyn StoreManager>);
type IntoIter = std::collections::hash_map::IntoIter<String, Arc<dyn StoreManager>>;

/// Returns a default store manager for a given label. Should only be called
/// if there is no runtime configuration for the label.
///
/// If `Option::None` is returned, the database is not allowed.
fn default_store(&self, label: &str) -> Option<Arc<dyn StoreManager>>;
fn into_iter(self) -> Self::IntoIter {
self.store_managers.into_iter()
}
}
Loading

0 comments on commit 3c087c3

Please sign in to comment.