DEV Community

Cover image for Power Patterns: Where Capability Composition Shines
bwi
bwi

Posted on

Power Patterns: Where Capability Composition Shines

In Part 1, we saw the problem Capability Composition solves. In Part 2, we learned the core API - Scope, Composer, and Composition.

Now comes the fun part.


My Production Use Case: Cocoar.Configuration

Before we explore possibilities, let's ground this in reality. Here's what I actually built and use in production:

The Problem: In Cocoar.Configuration, users configure types with a fluent API across multiple assemblies:

services.AddCocoarConfiguration(rules, configure => 
{
    configure.ConcreteType<AppSettings>()
        .AsSingleton()            // ← Extension from Cocoar.Configuration.DI
        .ExposeAs<IAppSettings>() // ← Core method from Cocoar.Configuration
        .DisableAutoRegistration(); // ← Another DI extension
});
Enter fullscreen mode Exit fullscreen mode

The Challenge:

  • Core library provides ConcreteConfigBuilder<T> (doesn't know about DI)
  • DI assembly adds extension methods that attach metadata (lifetimes, interface mappings)
  • Later, the DI registration process needs to read ALL metadata from ALL assemblies
  • Builder instances are created dynamically at runtime
  • Can't use inheritance, can't add fields to builders, can't use simple dictionaries

The Solution with Capabilities:

// 1. Core builder attaches primary capability (the type being configured)
public sealed class ConcreteConfigBuilder<T> : ConfigBuilder
{
    internal ConcreteConfigBuilder(CapabilityScope scope) : base(scope)
    {
        scope.For(this).WithPrimary(new ConcreteTypePrimary<ConfigBuilder>(typeof(T)));
    }
}

// 2. DI extension methods (different assembly!) attach secondary capabilities
public static ConcreteConfigBuilder<T> AsSingleton<T>(this ConcreteConfigBuilder<T> builder)
{
    ConfigBuilder.GetComposer(builder)
        .Add(new ServiceLifetimeCapability<ConfigBuilder>(ServiceLifetime.Singleton, null));
    return builder;
}

public static ConcreteConfigBuilder<T> ExposeAs<TInterface>(this ConcreteConfigBuilder<T> builder)
{
    ConfigBuilder.GetComposer(builder)
        .Add(new ExposeAsCapability<ConfigBuilder>(typeof(TInterface)));
    return builder;
}

// 3. DI registration retrieves and processes all capabilities
public static IServiceCollection AddCocoarConfiguration(
    this IServiceCollection services,
    ConfigManager configManager)
{
    foreach (var spec in configManager.Exposures)
    {
        if (!configManager.CapabilityScope.Compositions.TryGet(spec, out var composition))
            continue;

        // Get the primary type
        if (composition.TryGetPrimaryAs<ConcreteTypePrimary<ConfigBuilder>>(out var typeCapability))
        {
            var concreteType = typeCapability.SelectedType;

            // Check for flags
            if (composition.Has<DisableAutoRegistrationCapability<ConfigBuilder>>())
                continue; // Skip this one

            // Get all lifetimes and exposures
            var lifetimes = composition.GetAll<ServiceLifetimeCapability<ConfigBuilder>>();
            var exposures = composition.GetAll<ExposeAsCapability<ConfigBuilder>>();

            // Register services with DI container
            ProcessServiceRegistration(services, concreteType, lifetimes, exposures);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is real production code. It solves the cross-assembly metadata propagation problem that was blocking the Configure API implementation.

Everything else in this article explores what else becomes possible with this same pattern.


🎯 What This Article Is (And Isn't)

My production experience: Cocoar.Configuration - enriching configuration builders with metadata across assemblies (shown above).

This article: Thought experiments showing what else becomes possible with this pattern.

What you'll find here:

  • ✅ Showcase architectural possibilities
  • ✅ Stretch your mental model
  • ✅ Inspire solutions for future problems
  • ✅ Patterns that show the flexibility of the approach

What you WON'T find:

  • ❌ "Best practices from production"
  • ❌ "You should use this approach"
  • ❌ Fully-vetted production code for every example
  • ❌ Prescriptive solutions

Many of these could be solved more simply with other approaches. The goal is to open your mind to new architectural options, not prescribe solutions.

When you hit a problem where you need to attach metadata across assemblies, compose behavior dynamically, or extend types you don't own - you'll think: "Hey, capabilities could work here!"

Don't cargo-cult these patterns. Understand them, then use what makes sense for your context.


We'll explore:

  • Framework-free middleware pipelines
  • Enriching enums and primitives with metadata
  • Ordered execution without framework coupling
  • Extension fields for game development
  • Multi-tenant isolation and plugin architectures

Let's dive in.


Pattern 1: Framework-Free Ordered Pipelines 🔥

The Idea: What if you want ASP.NET Core-style middleware pipelines, but:

  • You're in a class library (no HTTP context)
  • You're building a background service
  • You don't want framework dependencies

The Traditional Approach:

// Tightly coupled to ASP.NET Core
app.Use(async (context, next) => 
{
    await LogRequest(context);
    await next();
});
Enter fullscreen mode Exit fullscreen mode

A Capability Composition Approach:

public record RequestContext(string UserId, Dictionary<string, object> Data);

// Create a pipeline definition object
public class RequestPipeline
{
    public string Name { get; init; } = "Standard Request Pipeline";
}

public class PipelineService
{
    private readonly CapabilityScope _scope = new();
    private readonly RequestPipeline _pipelineDefinition = new();

    public void Initialize()
    {
        // Define your pipeline once at startup with explicit ordering
        _scope.For(_pipelineDefinition)
            .Add<Func<RequestContext, Task>>(
                async ctx => await AuthenticateUser(ctx), 
                order: 1)
            .Add<Func<RequestContext, Task>>(
                async ctx => await ValidateInput(ctx), 
                order: 2)
            .Add<Func<RequestContext, Task>>(
                async ctx => await ExecuteBusinessLogic(ctx), 
                order: 3)
            .Add<Func<RequestContext, Task>>(
                async ctx => await AuditAction(ctx), 
                order: 4)
            .Build();
    }

    // Execute the pipeline for each request
    public async Task ProcessRequest(RequestContext context)
    {
        var pipeline = _scope.Compositions.GetRequired(_pipelineDefinition)
            .GetAll<Func<RequestContext, Task>>();

        foreach (var step in pipeline) // Guaranteed order!
        {
            await step(context);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why use an object as subject? Because the pipeline definition is set up once and reused many times. The object acts as the identifier for this pipeline configuration. When the object is garbage collected (e.g., when the service is disposed), the registry entry is automatically cleaned up.

What This Would Give You:

Zero framework dependencies - Pure .NET, works anywhere
Guaranteed execution order - Lower numbers run first
Type-safe - Full compile-time checking
Composable - Different pipelines for different scenarios
Testable - Each step is independently testable

Different Pipelines for Different Scenarios:

public class MessageProcessingService
{
    private readonly CapabilityScope _scope = new();
    private readonly object _pipelineDefinition = new();

    public void Initialize()
    {
        // Message processing pipeline
        _scope.For(_pipelineDefinition)
            .Add<Func<Message, Task>>(msg => ValidateSchema(msg), order: 1)
            .Add<Func<Message, Task>>(msg => EnrichMetadata(msg), order: 2)
            .Add<Func<Message, Task>>(msg => RouteToHandler(msg), order: 3)
            .Build();
    }

    public async Task ProcessMessage(Message message)
    {
        var pipeline = _scope.Compositions.GetRequired(_pipelineDefinition)
            .GetAll<Func<Message, Task>>();

        foreach (var step in pipeline)
        {
            await step(message);
        }
    }
}

// Or use a dedicated pipeline configuration class for clarity
public class ValidationPipelineConfig { }

public class UserValidator
{
    private readonly CapabilityScope _scope = new();
    private readonly ValidationPipelineConfig _config = new();

    public void Initialize()
    {
        // Validation pipeline (cheap validators first, expensive last!)
        _scope.For(_config)
            .Add<Func<User, ValidationResult>>(u => ValidateRequired(u), order: 1)
            .Add<Func<User, ValidationResult>>(u => ValidateFormat(u), order: 2)
            .Add<Func<User, ValidationResult>>(u => ValidateBusinessRules(u), order: 3)
            .Add<Func<User, ValidationResult>>(u => CheckDatabase(u), order: 4)
            .Build();
    }
}
Enter fullscreen mode Exit fullscreen mode

The Key Insight: Functions are capabilities too! When combined with ordering, you get pipeline orchestration without any framework.

Note on subject choice: Use an object instance as the subject (not a string) because:

  • The pipeline definition is set up once and reused
  • Reference types automatically clean up from the registry when no longer needed
  • It's clearer what the subject represents (a pipeline configuration, not arbitrary metadata)

Pattern 2: Enriching Enums with Metadata 🏷️

The Idea: You have error codes or status enums, and you need to attach display messages, severity levels, and handlers - but you can't modify the enum (it's from a third-party library, or it's domain code that shouldn't know about UI).

The Traditional Approach:

// Giant switch statement scattered everywhere
string GetErrorMessage(ErrorCode code)
{
    switch (code)
    {
        case ErrorCode.InvalidInput: return "Invalid input provided";
        case ErrorCode.Timeout: return "Request timed out";
        // ... 50 more cases
    }
}

// Repeated in multiple places with slightly different variations
Enter fullscreen mode Exit fullscreen mode

A Capability Composition Approach:

public enum ErrorCode { InvalidInput, Timeout, Unauthorized, NotFound }

public record DisplayCapability(string Message);
public record SeverityCapability(LogLevel Level);
public record ResponseCapability(int HttpStatus);

// Initialize once, during application startup
public void InitializeErrorMetadata()
{
    _scope.For(ErrorCode.InvalidInput)
        .Add(new DisplayCapability("The input provided was invalid"))
        .Add(new SeverityCapability(LogLevel.Warning))
        .Add(new ResponseCapability(400))
        .Build();

    _scope.For(ErrorCode.Timeout)
        .Add(new DisplayCapability("The request timed out"))
        .Add(new SeverityCapability(LogLevel.Error))
        .Add(new ResponseCapability(504))
        .Build();

    _scope.For(ErrorCode.Unauthorized)
        .Add(new DisplayCapability("You are not authorized"))
        .Add(new SeverityCapability(LogLevel.Warning))
        .Add(new ResponseCapability(401))
        .Build();
}

// Use anywhere in your application
public void HandleError(ErrorCode code)
{
    var composition = _scope.Compositions.Get(code);
    if (composition == null) return;

    var display = composition.GetFirstOrDefault<DisplayCapability>();
    var severity = composition.GetFirstOrDefault<SeverityCapability>();
    var response = composition.GetFirstOrDefault<ResponseCapability>();

    Logger.Log(severity.Level, display.Message);
    HttpContext.Response.StatusCode = response.HttpStatus;
}
Enter fullscreen mode Exit fullscreen mode

What This Would Give You:

Centralized metadata - One place to define all error information
No switch statements - Metadata lookup instead
Type-safe - Each capability is strongly typed
Extensible - Add new capability types without changing existing code
Domain separation - Domain enums stay clean, UI concerns live elsewhere

Remember from Part 2: Enum compositions persist in the registry for the application lifetime. This is a feature! You initialize once at startup, and the metadata is always available.

Advanced Pattern - Action Capabilities:

// Attach behavior directly to enum values
_scope.For(ErrorCode.Timeout)
    .Add(new DisplayCapability("Request timed out"))
    .Add<Action<HttpContext>>(ctx => RenderTimeoutPage(ctx))
    .Build();

_scope.For(ErrorCode.NotFound)
    .Add(new DisplayCapability("Not found"))
    .Add<Action<HttpContext>>(ctx => RenderNotFoundPage(ctx))
    .Build();

// Execute enum-specific behavior
var composition = _scope.Compositions.Get(errorCode);
var handler = composition?.GetFirstOrDefault<Action<HttpContext>>();
handler?.Invoke(httpContext);
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Capability Ordering Beyond Pipelines ⬆️

Ordering isn't just for pipelines - it's useful whenever you have multiple capabilities of the same type and execution order matters.

Event Handlers with Priority:

scope.For("OrderPlaced")
    // Critical - must succeed
    .Add<Func<Order, Task>>(ValidateOrder, order: 100)
    .Add<Func<Order, Task>>(ChargePayment, order: 100)

    // Important - should succeed
    .Add<Func<Order, Task>>(UpdateInventory, order: 200)
    .Add<Func<Order, Task>>(ReserveShipment, order: 200)

    // Nice to have - can fail gracefully
    .Add<Func<Order, Task>>(SendConfirmationEmail, order: 300)
    .Add<Func<Order, Task>>(UpdateAnalytics, order: 300)
    .Build();
Enter fullscreen mode Exit fullscreen mode

Validation with Fail-Fast:

// Order: cheap validations first, expensive last
scope.For(formData)
    .Add(new ValidationCapability("Required", ValidateRequired), order: 1)
    .Add(new ValidationCapability("Format", ValidateFormat), order: 2)
    .Add(new ValidationCapability("Business", ValidateBusinessRules), order: 3)
    .Add(new ValidationCapability("Database", CheckDatabase), order: 4)
    .Build();

// Execute until first failure
var validators = composition.GetAll<ValidationCapability>();
foreach (var validator in validators) // Returns in order
{
    if (!validator.IsValid(data))
    {
        return ValidationResult.Fail(validator.Name);
    }
}
Enter fullscreen mode Exit fullscreen mode

Property-Based Ordering:

public record TaskCapability(string Name, int Priority);

scope.For(taskManager)
    .Add(new TaskCapability("Critical", 1), cap => cap.Priority)
    .Add(new TaskCapability("High", 5), cap => cap.Priority)
    .Add(new TaskCapability("Normal", 10), cap => cap.Priority)
    .Build();

// Tasks returned in priority order
var tasks = composition.GetAll<TaskCapability>();
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Multi-Tenant Context Isolation 🏢

The Idea: Different tenants need different metadata for the same identifiers.

The Solution: Each tenant gets its own scope!

public class MultiTenantService
{
    private readonly Dictionary<string, CapabilityScope> _tenantScopes = new();

    public void RegisterTenant(string tenantId)
    {
        var scope = new CapabilityScope();
        _tenantScopes[tenantId] = scope;

        // Same enum = different meaning per tenant!
        scope.For(ErrorCode.InvalidInput)
            .Add(new DisplayCapability(GetTenantMessage(tenantId)))
            .Add(new SeverityCapability(GetTenantSeverity(tenantId)))
            .Build();

        // Tenant-specific configuration
        scope.For("MaxUploadSize")
            .Add(new LimitCapability(GetTenantLimit(tenantId)))
            .Build();

        scope.For("BrandColors")
            .Add(new ThemeCapability(GetTenantTheme(tenantId)))
            .Build();
    }

    public void ProcessForTenant(string tenantId, ErrorCode error)
    {
        var scope = _tenantScopes[tenantId];
        var composition = scope.Compositions.Get(error);

        // Tenant-specific error handling
        var display = composition?.GetFirst<DisplayCapability>();
        Console.WriteLine(display?.Message); // Tenant-specific message!
    }
}
Enter fullscreen mode Exit fullscreen mode

What This Would Give You:

Complete isolation - Tenants can't interfere with each other
Same API - No special multi-tenant handling in business logic
Value type power - Enums and strings work perfectly as stable identifiers


Pattern 5: Plugin Architecture with Self-Description 🔌

The Idea: Build a plugin system where plugins can describe their own capabilities.

public interface IPlugin
{
    void RegisterCapabilities(CapabilityScope scope);
}

public class EmailPlugin : IPlugin
{
    public void RegisterCapabilities(CapabilityScope scope)
    {
        scope.For(this)
            .WithPrimary(new PluginMetadata(
                name: "Email Plugin",
                version: "1.2.0",
                author: "Acme Corp"))
            .AddAs<(IEmailSender, INotificationProvider)>(new EmailService())
            .Add(new DependencyCapability("SMTP", "1.0+"))
            .Add(new FeatureCapability("Templates"))
            .Add(new FeatureCapability("Attachments"))
            .Build();
    }
}

// Plugin host discovers capabilities
public class PluginHost
{
    private readonly CapabilityScope _scope = new();
    private readonly List<IPlugin> _plugins = new();

    public void LoadPlugin(IPlugin plugin)
    {
        plugin.RegisterCapabilities(_scope);
        _plugins.Add(plugin);
    }

    // Query plugins by capability
    public IEnumerable<T> GetCapabilities<T>()
    {
        return _plugins
            .Select(p => _scope.Compositions.Get(p))
            .Where(c => c != null)
            .SelectMany(c => c.GetAll<T>());
    }

    // Find plugins with specific features
    public IEnumerable<IPlugin> GetPluginsWithFeature(string feature)
    {
        return _plugins
            .Where(p => {
                var comp = _scope.Compositions.Get(p);
                var features = comp?.GetAll<FeatureCapability>() ?? [];
                return features.Any(f => f.Name == feature);
            });
    }

    // Check dependencies
    public bool ValidatePlugin(IPlugin plugin)
    {
        var comp = _scope.Compositions.Get(plugin);
        var deps = comp?.GetAll<DependencyCapability>() ?? [];

        return deps.All(dep => IsDepSatisfied(dep));
    }
}
Enter fullscreen mode Exit fullscreen mode

What Makes This Interesting:

Self-describing - Plugins declare their own capabilities
Discoverable - Query plugins by any capability
Type-safe - No casting or reflection needed
Flexible - Easy to add new capability types


Pattern 6: Extension Fields for Game Development 🎮

The Idea: You have objects you can't modify (third-party classes, framework types) and you need to attach mutable state - data that changes over time, not just readonly metadata.

Key Insight: Capabilities aren't just data bags - they can have mutable state AND methods! This is essentially Entity Component System (ECS) without the framework.

Example: Adding Combat Stats to a Third-Party Enemy Class

// Third-party class you can't modify
public class Enemy
{
    public string Name { get; }
    public int AttackPower { get; }

    public void Attack() 
    {
        Console.WriteLine($"{Name} attacks for {AttackPower} damage!");
    }
}

// Capability with MUTABLE state and methods
public class CombatModifiers
{
    public float AttackMultiplier { get; set; } = 1.0f;
    public List<string> ActiveEffects { get; } = new();

    public void ApplyBuff(float boost, string effectName)
    {
        AttackMultiplier += boost;
        ActiveEffects.Add(effectName);
    }

    public int CalculateModifiedDamage(int baseDamage)
        => (int)(baseDamage * AttackMultiplier);
}

// Extension method for ergonomic usage
public static class EnemyExtensions
{
    private static readonly CapabilityScope _scope = new();

    public static void EnhancedAttack(this Enemy enemy, Enemy target)
    {
        // Get or create capability
        var composition = _scope.Compositions.Get(enemy) 
            ?? _scope.For(enemy).Add(new CombatModifiers()).Build();

        var modifiers = composition.GetFirst<CombatModifiers>();
        int damage = modifiers.CalculateModifiedDamage(enemy.AttackPower);

        Console.WriteLine($"{enemy.Name} attacks for {damage} damage!");
    }

    public static void ApplyBuff(this Enemy enemy, float boost)
    {
        var composition = _scope.Compositions.Get(enemy) 
            ?? _scope.For(enemy).Add(new CombatModifiers()).Build();

        var modifiers = composition.GetFirst<CombatModifiers>();
        modifiers.ApplyBuff(boost, "Buffed");

        Console.WriteLine($"{enemy.Name} buffed! Multiplier: {modifiers.AttackMultiplier:F2}");
    }
}
Enter fullscreen mode Exit fullscreen mode
var orc = new Enemy("Orc", 10);

orc.Attack();                    // Orc attacks for 10 damage!
orc.ApplyBuff(0.5f);            // Orc buffed! Multiplier: 1.50
orc.EnhancedAttack(goblin);     // Orc attacks for 15 damage!
Enter fullscreen mode Exit fullscreen mode

This is ECS without the framework:

  • Entity = The subject (Enemy instance)
  • Components = Capabilities (CombatModifiers, StatusEffects, etc.)
  • Systems = Your code that operates on capabilities

What This Would Give You:

No source code modification - Extend third-party types freely
Mutable state - Data changes over time (buffs, stats, effects)
Methods on capabilities - Encapsulated behavior (ApplyBuff, CalculateModifiedDamage)
Automatic memory management - When enemy is GC'd, capabilities are too (ConditionalWeakTable)
No inheritance hierarchy - Compose behavior dynamically
Multiple systems - Different systems can add different capabilities independently

Game Development Use Cases:

  • RPG Stats: Health, mana, buffs, debuffs, equipment modifiers
  • AI State: Behavior trees, blackboards, decision-making data
  • Physics Extensions: Custom collision data, particle effects, force modifiers
  • Rendering Metadata: LOD info, culling data, shader parameters
  • Audio Context: 3D positioning, reverb zones, sound profiles
  • Networking State: Sync flags, ownership, replication metadata

General Patterns: Scope Lifetime & Error Handling

These patterns apply whether you use capabilities for configuration builders (like I do) or for any of the other scenarios.

Pattern: Scope Lifetime Strategy

// ✅ Application-wide scope (singleton)
public class Application
{
    private readonly CapabilityScope _appScope = new();

    // Use for:
    // - Enum metadata
    // - Configuration strings
    // - Application constants
}

// ✅ Per-tenant scope
public class TenantManager
{
    private readonly Dictionary<string, CapabilityScope> _tenantScopes = new();

    // Use for:
    // - Tenant-specific metadata
    // - Context isolation
}

// ✅ Per-request scope
public void HandleRequest(Request request)
{
    using var requestScope = new CapabilityScope();

    // Use for:
    // - Request-specific capabilities
    // - Automatic cleanup
}

// ✅ Per-test scope
[Test]
public void MyTest()
{
    using var testScope = new CapabilityScope();

    // Use for:
    // - Test isolation
    // - Clean slate per test
}
Enter fullscreen mode Exit fullscreen mode

Pattern: Registry Decision Matrix

Scenario UseCompositionRegistry When
Build once, use many places ✅ Enabled (default) Most common
Build & use in same method ❌ Disabled Performance-critical
Pass composition directly ❌ Disabled Explicit data flow
Lookup by subject needed ✅ Enabled Convenience

Pattern: Error Handling

// Use GetRequired when capability MUST exist
var editor = composition.GetRequiredFirst<EditCapability>();

// Use TryGet when capability is optional
if (composition.TryGetFirst<PrintCapability>(out var printer))
{
    printer.Print(document);
}

// Use Get with null check for registries
var comp = scope.Compositions.Get(document);
if (comp == null)
{
    // Handle missing composition
    throw new InvalidOperationException("Document not registered");
}
Enter fullscreen mode Exit fullscreen mode

Summary: When to Consider Each Pattern

Pattern Could Work For Key Characteristic
Ordered Pipelines Middleware, validation, processing Framework-free orchestration
Enum Enrichment Error codes, status values Centralized metadata
Capability Ordering Priority execution, event handlers Guaranteed order
Multi-Tenant SaaS applications Complete isolation
Plugin Architecture Extensibility systems Self-describing capabilities
Extension Fields Game dev, extending third-party types ECS without framework

What Makes This Pattern Different

After using Capability Composition in production (for Cocoar.Configuration), here's what makes it unique:

It's not just one thing - it's the combination:

  • Works with any type (objects, enums, strings, functions)
  • Ordered execution built-in
  • Cross-assembly extensibility
  • Type-safe throughout
  • Zero dependencies
  • Immutable by default
  • Thread-safe without locks

No other pattern gives you all of these together.


Final Thoughts

I extracted this pattern from Cocoar.Configuration to solve a specific problem: attaching metadata to configuration builders across assembly boundaries. Extension methods from the DI assembly needed to add lifetime information, interface mappings, and flags - all without the core library knowing about DI.

That's my real production use case. Configuration builders with cross-assembly metadata.

But once you have that foundation - once you can attach typed metadata to any object and retrieve it later - you start seeing other possibilities:

  • Could this work for framework-free pipelines?
  • What about multi-tenant isolation with separate scopes?
  • Extension fields for game development?
  • Plugin systems with self-describing capabilities?

I haven't built most of these patterns in production. They're explorations based on what the core mechanism enables. Some might be perfect for your scenario. Some might be overkill. Some definitely have simpler alternatives.

The real value isn't that you should use all these patterns. It's that when you hit a problem where you need to:

  • Attach metadata across assemblies
  • Compose behavior dynamically
  • Extend types you don't own
  • Build plugin architectures

...you have this pattern available. Not a silver bullet, just another tool in your architectural toolkit.


Try It Yourself

GitHub: github.com/cocoar-dev/Cocoar.Capabilities
NuGet: Cocoar.Capabilities
Real-world usage: Cocoar.Configuration (the production use case)

Have a scenario where this might fit? I'd love to hear about your architectural challenges and whether Capability Composition could help solve them.


This is Part 3 (final) in the Capability Composition series.

← Part 1: The Cross-Assembly Metadata Problem

← Part 2: Building with Capabilities

Top comments (0)