Skip to main content

Schema Derivation

A single connector type — say, Twilio SMS — can serve many different use cases. A development environment should not send real messages. A basic-tier environment should not have access to media messaging. An outbound-only integration does not need webhook parameters. These are all variations of the same connector, differing only in which capabilities, parameters, and constraints apply.

Schema derivation lets you model this without duplication. You define a master schema that represents the full capability of the connector, then derive restricted schemas that inherit the base structure while removing or tightening specific aspects. The derived schema keeps the same logical identity as the base, so the registry and compatibility checks continue to work — but the connector created from a derived schema behaves according to the restrictions applied.

This is the primary mechanism for environment-specific settings and feature-tier management.

Copy constructor

The copy constructor clones all properties from the source schema:

  • Channel provider, type, and version (the logical identity)
  • Capabilities
  • Parameters (full deep copy with all constraints)
  • Content types
  • Message property configurations
  • Endpoint configurations
  • Authentication configurations
  • Strict/flexible mode

The new schema is fully independent — changes to the derived schema do not affect the source.

var baseSchema = new ChannelSchema("Twilio", "SMS", "1.0")
.WithCapabilities(ChannelCapability.SendMessages | ChannelCapability.ReceiveMessages)
.AddRequiredParameter("AccountSid", DataType.String)
.AddRequiredParameter("AuthToken", DataType.String)
.AddParameter(new ChannelParameter("WebhookUrl", DataType.String))
.AddContentType(MessageContentType.PlainText)
.AddContentType(MessageContentType.Media)
.HandlesMessageEndpoint(EndpointType.PhoneNumber);

var outboundOnly = new ChannelSchema(baseSchema, "Outbound only")
.RemoveCapability(ChannelCapability.ReceiveMessages)
.RemoveParameter("WebhookUrl")
.RestrictContentTypes(MessageContentType.PlainText);

The derived schema retains "Twilio/SMS/1.0" as its logical identity. This means IsCompatibleWith(baseSchema) returns true, and ValidateAsRestrictionOf(baseSchema) can validate the restriction is valid.

Restriction methods

MethodEffectUse case
RemoveCapability(ChannelCapability)Remove one or more capability flagsDisable receive/status for outbound-only connectors
RemoveParameter(string)Drop a parameterRemove webhook URL when not receiving
UpdateParameter(string, Action<ChannelParameter>)Change parameter propertiesSet environment-specific defaults
RemoveMessageProperty(string)Drop a message propertyRemove unused properties
UpdateMessageProperty(string, Action<MessagePropertyConfiguration>)Change property constraintsTighten validation rules
RemoveContentType(MessageContentType)Remove a content typeDisable media for basic tier
RestrictContentTypes(params MessageContentType[])Replace the content type setAllow only text for SMS-only tier
RemoveEndpoint(EndpointType)Remove an endpoint configurationDisable email endpoints on SMS connector
UpdateEndpoint(EndpointType, Action<ChannelEndpointConfiguration>)Modify endpoint settingsChange send/receive direction
RemoveAuthenticationType(AuthenticationType)Remove an auth configurationRemove unsupported auth methods
RestrictAuthenticationTypes(params AuthenticationType[])Replace auth typesForce a specific auth method
RemoveAuthenticationConfiguration(...)Remove by configurationRemove a specific auth config
RestrictAuthenticationConfigurations(...)Replace auth configsSet allowed auth configs
RestrictCapabilities(params ChannelCapability[])Replace capabilitiesSet exact capability set

Validating restrictions

Derivation is a powerful tool, but unrestricted schemas can produce confusing errors at runtime — for example, a derived schema that removes a required parameter the connector expects, or that adds a capability the base never had. The framework provides ValidateAsRestrictionOf to catch these issues at configuration time, before connectors are created.

Before using a derived schema in production, always validate it is a compatible restriction of its base:

var issues = outboundOnly.ValidateAsRestrictionOf(baseSchema);
if (issues.Any())
{
foreach (var issue in issues)
Console.WriteLine($"Schema violation: {issue.ErrorMessage}");
throw new InvalidOperationException("Incompatible schema");
}

ValidateAsRestrictionOf checks:

  • Same channel provider
  • Same channel type
  • Same version
  • Derived capabilities are a subset of base capabilities
  • Derived parameters are a subset of base parameters (same names, compatible types)
  • Derived content types are a subset of base content types
  • Derived endpoints are a subset of base endpoints
  • Derived message properties are a subset of base message properties

Multi-level derivation

You can chain derivations:

var baseSchema = CreateMasterSchema();

var enterprise = new ChannelSchema(baseSchema, "Enterprise")
.RemoveCapability(ChannelCapability.ReceiveMessages);

var enterpriseGold = new ChannelSchema(enterprise, "Enterprise Gold")
.UpdateParameter("Timeout", p => p.DefaultValue = 10000)
.AddContentType(MessageContentType.Media);

Keep chains shallow (1-3 levels). Each level adds complexity; document what each level changes.

Practical examples

Environment-specific schemas with runtime settings

var master = LoadMasterSchema("twilio-sms");

var instanceSchema = new ChannelSchema(master, $"Instance {instanceId}")
.UpdateParameter("WebhookUrl", p =>
p.DefaultValue = $"https://{instanceId}.example.com/webhook")
.UpdateParameter("AccountSid", p =>
p.DefaultValue = settings.AccountSid);

Environment-specific schemas

var development = new ChannelSchema(productionSchema, "Development")
.UpdateParameter("Timeout", p => p.DefaultValue = 60000)
.UpdateParameter("SandboxMode", p => p.DefaultValue = true);

Feature-tier schemas

var basic = new ChannelSchema(baseSchema, "Basic")
.RemoveCapability(ChannelCapability.MediaAttachments)
.RemoveCapability(ChannelCapability.Templates)
.RestrictContentTypes(MessageContentType.PlainText);

var premium = new ChannelSchema(baseSchema, "Premium")
.AddContentType(MessageContentType.Media)
.AddContentType(MessageContentType.Template);

Runtime schema selection from registry

var registry = serviceProvider.GetRequiredService<IChannelSchemaRegistry>();
var master = registry.FindSchema("Twilio", "SMS");

var instanceSchema = new ChannelSchema(master, instanceName)
.UpdateParameter("WebhookUrl", p =>
p.DefaultValue = webhookUrl);

Good practices

  • Start from a complete master schema — the base should represent the full capability of the connector. Restrict from there.
  • Validate every derived schema before creating connectors from it. Use ValidateAsRestrictionOf.
  • Use descriptive display names — the derivedDisplayName parameter appears in logs and registry queries.
  • Keep derivation chains shallow — 1-3 levels is usually enough.
  • Document what each level changes — especially when schemas are used across different deployment environments.