第2回:Ohama - 横断的関心事の実装
~モナドによる関心事分離とパターンの自動適用~
はじめに
前回のアーキテクチャ概観を踏まえ、今回はOhamaフレームワークの詳細な実装を解説します。Ohamaフレームワークは以下の特徴を持ちます:
- モナドベースの関心事分離
- デザインパターンの自動適用
- ボイラープレートの削減
- 透過的な機能追加
モナドベースの関心事分離
// 基本的なモナド型の定義
pub struct Concern<T> {
value: T,
context: ConcernContext,
}
impl<T> Concern<T> {
pub fn new(value: T) -> Self {
Self {
value,
context: ConcernContext::default(),
}
}
pub fn map<U, F>(self, f: F) -> Concern<U>
where
F: FnOnce(T) -> U,
{
Concern {
value: f(self.value),
context: self.context,
}
}
pub fn and_then<U, F>(self, f: F) -> Concern<U>
where
F: FnOnce(T) -> Concern<U>,
{
let result = f(self.value);
Concern {
value: result.value,
context: self.context.merge(result.context),
}
}
}
// 横断的関心事の実装例
pub trait CrossCuttingConcern {
fn before<T>(&self, value: &T, context: &ConcernContext);
fn after<T>(&self, value: &T, context: &ConcernContext);
fn on_error(&self, error: &Error, context: &ConcernContext);
}
// ロギング関心事の実装
pub struct LoggingConcern {
logger: Logger,
}
impl CrossCuttingConcern for LoggingConcern {
fn before<T>(&self, value: &T, context: &ConcernContext) {
self.logger.info(&format!("Starting operation: {:?}", value));
}
fn after<T>(&self, value: &T, context: &ConcernContext) {
self.logger.info(&format!("Operation completed: {:?}", value));
}
fn on_error(&self, error: &Error, context: &ConcernContext) {
self.logger.error(&format!("Operation failed: {}", error));
}
}
デザインパターンのフレームワーク化
// パターンレジストリの実装
pub struct PatternRegistry {
patterns: HashMap<PatternId, Box<dyn Pattern>>,
}
impl PatternRegistry {
pub fn register<P: Pattern + 'static>(&mut self, pattern: P) {
self.patterns.insert(pattern.id(), Box::new(pattern));
}
pub fn apply_pattern<T>(&self, pattern_id: PatternId, target: T) -> Result<T>
where
T: ApplyPattern,
{
let pattern = self.patterns.get(&pattern_id)
.ok_or(Error::PatternNotFound)?;
pattern.apply(target)
}
}
// パターン適用のトレイト
pub trait Pattern: Send + Sync {
fn id(&self) -> PatternId;
fn apply<T: ApplyPattern>(&self, target: T) -> Result<T>;
}
// デコレータパターンの実装例
pub struct DecoratorPattern<T> {
id: PatternId,
decorator: Box<dyn Fn(T) -> Result<T>>,
}
impl<T: 'static> Pattern for DecoratorPattern<T> {
fn id(&self) -> PatternId {
self.id
}
fn apply<U: ApplyPattern>(&self, target: U) -> Result<U> {
if let Some(value) = target.as_any().downcast_ref::<T>() {
let decorated = (self.decorator)(value.clone())?;
Ok(decorated.into())
} else {
Err(Error::TypeMismatch)
}
}
}
パターンの自動適用メカニズム
// パターン自動適用のマクロ
#[macro_export]
macro_rules! apply_patterns {
($target:expr, $($pattern:expr),*) => {{
let mut result = $target;
$(
result = $pattern.apply(result)?;
)*
Ok(result)
}}
}
// パターン適用の属性マクロ
#[proc_macro_attribute]
pub fn pattern(attr: TokenStream, item: TokenStream) -> TokenStream {
let pattern_id: PatternId = syn::parse(attr).unwrap();
let input = syn::parse::<ItemFn>(item).unwrap();
// パターン適用のコード生成
quote! {
#[allow(non_snake_case)]
pub fn #input.ident #input.sig {
let result = #input.block;
PATTERN_REGISTRY.apply_pattern(#pattern_id, result)
}
}.into()
}
実装例:ロギングとトレーシングの統合
// ログとトレースを統合した関心事の実装
pub struct LogTrace {
logger: Logger,
tracer: Tracer,
}
impl CrossCuttingConcern for LogTrace {
fn before<T>(&self, value: &T, context: &ConcernContext) {
let span = self.tracer.create_span("operation");
self.logger.info_with_span(&span, &format!("Starting: {:?}", value));
context.set_span(span);
}
fn after<T>(&self, value: &T, context: &ConcernContext) {
if let Some(span) = context.get_span() {
self.logger.info_with_span(span, &format!("Completed: {:?}", value));
span.end();
}
}
}
// 使用例
#[pattern(log_trace)]
fn process_data(data: &str) -> Result<String> {
// データ処理ロジック
Ok(data.to_uppercase())
}
// 実行時の自動適用
fn main() -> Result<()> {
let result = Concern::new("hello world")
.map(|s| process_data(s)?)?
.and_then(|s| validate_data(s))?
.value;
println!("Result: {}", result);
Ok(())
}
モナド変換子の実装
// モナド変換子の基本実装
pub struct ConcernT<M> {
inner: M,
context: ConcernContext,
}
impl<M, T> ConcernT<M>
where
M: Monad<T>,
{
pub fn new(monad: M) -> Self {
Self {
inner: monad,
context: ConcernContext::default(),
}
}
pub fn lift<U>(monad: M) -> ConcernT<M>
where
M: Monad<U>,
{
ConcernT::new(monad)
}
pub fn run(self) -> M {
self.inner
}
}
今回のまとめ
- モナドベースの関心事分離により、横断的な機能を透過的に適用できる
- デザインパターンのフレームワーク化で、パターンの再利用性が向上
- 属性マクロによる自動適用で、ボイラープレートを削減
- モナド変換子により、異なるモナドの組み合わせが可能
次回予告
第3回では、cusinartフレームワークの分散処理基盤について解説します。透過的な分散処理の実装とノード間通信の最適化を中心に、具体的な実装例を示していきます。
参考資料
- Aspect-Oriented Programming in Rust
- Monad Transformers Step by Step
- Design Patterns: Elements of Reusable Object-Oriented Software
- Attribute Macros in Rust