# Kanject.Core.Packages

The strategy pattern, generated. When a port can have more than one implementation — Stripe or Paystack behind `IPaymentGateway`, one carrier or another behind `IOrderShipping` — you declare the interface and tag the providers, and the source generator emits the resolver, the manager, and the keyed DI registrations. Adding a provider is adding one class. Free under the Community licence.

It replaces the shapes multi-provider code usually decays into:

- Hand-written `switch` statements scattered across services
- One giant factory that knows every provider in the application
- `IServiceProvider` lookups with string keys at business call sites
- Host-specific plugin glue that libraries cannot reuse

The provider registry stays source-generated — no reflection, no assembly scanning, Native AOT compatible — while the business rule for *which* provider stays in your code.

> **Not NuGet packaging:** A *package* here is a port and its interchangeable providers, not a `.nupkg`. To version and release your own NuGet libraries, see [Publish libraries](https://www.kanject.com/docs/packages/).

## Install

```bash
dotnet add package Kanject.Core.Packages
dotnet add package Kanject.Core.Packages.Annotations
```

Add both to the project that declares the interface and to every project that declares a provider for it:

```xml
<ItemGroup>
  <PackageReference Include="Kanject.Core.Packages" />
  <PackageReference Include="Kanject.Core.Packages.Annotations" PrivateAssets="all" />
</ItemGroup>
```

> **Delete the `IncludeAssets` line:** `dotnet add package` writes an `IncludeAssets` line for the Annotations package that omits `compile`. Delete that line and keep `PrivateAssets="all"`, or the attribute types can be stripped from compilation.

## Define the port

```csharp
using Kanject.Core.Packages.Annotations.Attributes;

// The port. The package id names everything the generator emits:
// "payment.gateway" → IPaymentGatewayPackageManager, IPaymentGatewayResolver.
[Package(id: "payment.gateway")]
[PackageDescription("Process order payments through third-party gateways")]
public interface IPaymentGateway
{
    Task<PurchaseOrderResponse> PurchaseOrderAsync(
        PurchaseOrderRequest request, Guid userId);

    Task ProcessWebhookAsync(WebhookEvent webhookEvent);
}
```

## Tag the providers

Each implementation names the port it serves, its own id and version, and a short resolver key. Make the class `partial` so the generator can add its metadata properties. `[DefaultPackageProvider]` marks the fallback:

```csharp
using Kanject.Core.Packages.Annotations.Attributes;

[Package<IPaymentGateway>]
[PackageProvider(id: "stripe.payment.gateway", version: 1)]
[PackageResolverKey("stripe")]
[DefaultPackageProvider]
public sealed partial class StripePaymentGateway : IPaymentGateway
{
    // implementation
}

[Package<IPaymentGateway>]
[PackageProvider(id: "paystack.payment.gateway", version: 1)]
[PackageResolverKey("paystack")]
public sealed partial class PaystackPaymentGateway : IPaymentGateway
{
    // implementation
}
```

## Register

```csharp
// Keyed providers, manifests, the manager, and the key resolver.
services.AddPaymentGatewayPackages();
```

The extension is generated next to the providers, in a namespace named after their assembly. Provider assemblies compose through a container-local catalog, so sibling assemblies that never reference each other still resolve together.

## Pick a provider

Two ways, over the same resolver and the same keyed registrations. Use the generated manager when the call site intentionally chooses — webhooks, admin tools, tests:

```csharp
// Explicit: the call site names the provider.
public sealed class PaymentWebhookHandler(
    IPaymentGatewayPackageManager paymentGateways)
{
    // A generated, strongly-typed method per provider and version…
    public Task HandleStripeAsync(WebhookEvent webhookEvent)
        => paymentGateways
            .UseStripePaymentGatewayV1Package()
            .ProcessWebhookAsync(webhookEvent);

    // …or dispatch on data: a key from a route, a tenant, a config row.
    public Task HandleAsync(string gatewayKey, WebhookEvent webhookEvent)
        => paymentGateways
            .ResolvePackage(gatewayKey)
            .ProcessWebhookAsync(webhookEvent);
}
```

Or set an `IPackageScope` for the unit of work and inject the port itself. With no scope registered, the port resolves to the default provider:

```csharp
using Kanject.Core.Packages.Engine;
using Kanject.Core.Packages.Interfaces;

// Ambient: choose once for the unit of work…
var packageScope = PackageScopeBuilder.Create()
    .Use<IPaymentGateway>("paystack")
    .Build();

services.AddScoped<IPackageScope>(_ => packageScope);

// …and business code injects the bare port. No provider names here.
public sealed class CheckoutWorkflow(IPaymentGateway paymentGateway)
{
    public Task RunAsync(Guid orderId, Guid userId)
        => paymentGateway.PurchaseOrderAsync(
            new PurchaseOrderRequest { OrderId = orderId }, userId);
}
```

A scope can pin a version too — `.Use<IPaymentGateway>("stripe", version: 1)`. Leave the version off and the highest registered version for that key is used.

## Put the routing rule in one place

The generated resolver maps `[PackageResolverKey]` values to providers and needs no code from you. When the choice depends on your own data — the buyer's country, the tenant's plan — replace it with your own. That is the one place a `switch` survives, and every call site and every scope follows it:

```csharp
// Your routing rule, in one place. The generator emits the abstract base.
public sealed class PaymentGatewayResolver(
    ICountryPaymentSettings countrySettings)
    : AbstractPaymentGatewayResolver
{
    public override (string packageId, int version) GetDefaultPackage()
        => this.UseStripePaymentGatewayV1Package();

    public override async Task<(string packageId, int version)> ResolvePackageWithAsync(
        Dictionary<string, string> args)
    {
        // Country → gateway key, from your own configuration.
        var key = await countrySettings.GetGatewayKeyAsync(args["countryId"]);
        return key is null ? GetDefaultPackage() : ResolvePackageWith(key, version: null);
    }

    // Key → provider. The base class throws until you supply this.
    public override (string packageId, int version) ResolvePackageWith(
        string key, int? version)
        => key switch
        {
            "stripe" when version is null or 1 => this.UseStripePaymentGatewayV1Package(),
            "paystack" when version is null or 1 => this.UsePaystackPaymentGatewayV1Package(),
            _ => throw new KeyNotFoundException(
                $"No provider matches key '{key}' for {nameof(IPaymentGateway)}."),
        };
}

services.AddPaymentGatewayResolver<PaymentGatewayResolver>(ServiceLifetime.Scoped);
services.AddPaymentGatewayPackages();
```

## Fan out to every provider

Some calls should reach all providers rather than one — collecting quotes, broadcasting a notification. Declare an aggregator and the generator writes one method per port method that calls each listed provider and awaits them together; a `Task<T>` method returns `Task<IList<T>>`:

```csharp
using Kanject.Core.Packages.Annotations.Attributes;

// Ask every carrier for a quote, in one call.
[ProviderAggregator(typeof(IShippingQuotes))]
[PackageProvider<ExpressCourierQuotes>]
[PackageProvider<EconomyCourierQuotes>]
public partial class ShippingQuoteAggregator
{
}

services.AddShippingQuoteAggregator();
```

## Validate at startup

The analyzers catch static miswiring at compile time, but they cannot see the container — a provider assembly nobody registered, or a config value that selects a key no provider answers to. One opt-in call checks the live graph before the first request:

```csharp
using Kanject.Core.Packages.Diagnostics.Validators;

var app = services.BuildServiceProvider();
app.ValidatePackageGraphOrThrow();   // PackageGraphValidationException on any error
```

## What ships with it

- `[Package(id)]` on the interface; `[Package<TPackage>]`, `[PackageProvider(id, version)]`, `[PackageResolverKey]`, and `[DefaultPackageProvider]` on providers
- Generated `I{Package}PackageManager` and `I{Package}Resolver`, plus an `Add{Package}Packages()` registration per provider assembly
- A strongly-typed `Use{Provider}V{n}Package()` method per provider and version, alongside key-based `ResolvePackage(key)`
- `[ProviderAggregator]` for fan-out — one generated call that reaches every listed provider
- Keyed DI underneath — scoped by default, per-provider `Lifetime` override, no reflection and Native AOT compatible
- `IPackageScope` and `PackageScopeBuilder` for ambient, per-request provider choice with optional version pinning
- Side-by-side provider versions — pin one for a staged rollout or a cohort migration, or take the latest registered
- Cross-assembly composition — provider assemblies that never reference each other resolve through one catalog
- `ValidatePackageGraph()` / `ValidatePackageGraphOrThrow()` — a startup check of the composed container, with `KANPKG` codes naming what is missing

> **Pairs with Features:** Packages decides which provider handles a port; [Features](https://www.kanject.com/docs/core-features/) decides whether that provider ships or runs. Use them together when a provider is specific to an edition, a customer, or a region.

> **Free under the Community licence:** `Kanject.Core.Packages` and its generator are published on public NuGet and free for individuals, open source, education, and companies under $250K.

---
_Source: https://www.kanject.com/docs/core-packages/ · Kanject Docs_
