Guia — IDataProvider
Um banco de dados novo, ativado por provider: num spec.dataSources. QuerySpec carrega
filtros/ordenação/paginação numa forma neutra — não há obrigação de suportar SQL.
Exemplo mínimo
using Pipevine.Abstractions;
using Pipevine.Core.Configuration;
using Pipevine.Data.Abstractions;
using Pipevine.Model.Resources;
// A chave usada no YAML: `provider: acmedb`
[Provider("acmedb", ProviderKind.DataSource)]
public sealed class AcmeDataProvider : IDataProvider
{
public IDataSession CreateSession(DataSourceDefinition definition)
{
var config = PipevineNodeJsonConverter.ToJsonNode(definition.Config);
var connectionString = config?["connectionString"]?.GetValue<string>()
?? throw new DataProviderConfigurationException($"Data source '{definition.Id}' requer 'connectionString'.");
return new AcmeDataSession(connectionString);
}
}
IDataSession : IPipelineTransaction — é literalmente a transação que o executor do pipeline
comita/reverte quando a rota declara transaction: required (o motor de pipeline só conhece o
contrato genérico, nunca que é especificamente um banco de dados):
internal sealed class AcmeDataSession(string connectionString) : IDataSession
{
public IEntityStore GetStore(string entityName) => new AcmeEntityStore(entityName /* ... */);
public ValueTask<int> ExecuteSqlAsync(string sql, IReadOnlyDictionary<string, JsonNode?> parameters, CancellationToken ct) =>
throw new NotSupportedException("acmedb não tem dialeto SQL — use os steps db.* tipados.");
public ValueTask CommitAsync(CancellationToken ct) { /* ... */ }
public ValueTask RollbackAsync(CancellationToken ct) { /* ... */ }
public ValueTask DisposeAsync() { /* ... */ }
}
db.sql é o escape hatch declarado no vocabulário do YAML — um provider não relacional não precisa
implementá-lo de verdade; lançar NotSupportedException é aceitável quando esse caminho realmente
não existe no banco de destino.
IEntityStore é onde QuerySpec entra:
internal sealed class AcmeEntityStore(string entityName) : IEntityStore
{
public ValueTask<JsonNode?> FindAsync(QuerySpec spec, CancellationToken ct) { /* traduza spec.Filters */ }
public ValueTask<bool> ExistsAsync(QuerySpec spec, CancellationToken ct) { /* ... */ }
public ValueTask<IReadOnlyList<JsonNode>> QueryAsync(QuerySpec spec, CancellationToken ct) { /* spec.Sort/Skip/Take */ }
public ValueTask PersistAsync(JsonNode data, OnConflictSpec? onConflict, CancellationToken ct) { /* ... */ }
public ValueTask UpdateAsync(QuerySpec spec, JsonNode patch, CancellationToken ct) { /* ... */ }
public ValueTask DeleteAsync(QuerySpec spec, CancellationToken ct) { /* ... */ }
}
QuerySpec.Filters cobre oito operadores (Eq/Ne/Gt/Gte/Lt/Lte/Contains/In) — se o
seu banco não conseguir expressar algum deles, isso é sinal de que vale revisitar como a abstração
está sendo usada, não que você deva forçar um NotSupportedException silencioso num caso comum.
Ativação
spec:
dataSources:
- { id: main, provider: acmedb, connectionString: "${env:ACME_DB_CONNECTION}" }
entities:
- { name: item, table: items, dataSource: main, key: id, properties: [...] }
services.AddKeyedSingleton<IDataProvider, AcmeDataProvider>("acmedb");
// mais services.AddPipevineData() do core, que registra os steps genéricos db.persist/find/...
Se provider: acmedb aparecer no YAML sem essa chave registrada, o build falha com um diagnóstico
claro:
orders.pipevine.yaml(14,20): error PV3010: nenhum provider de dados registrado
para a chave 'acmedb'. Referencie o pacote que a fornece.
Outbox transacional (opcional)
Um provider que também quer participar do outbox transacional (usado por publish com
mode: outbox) implementa IOutboxStore separadamente.
Para onde ir daqui
- Visão geral de extensibilidade
- Persistência — como
dataSources/entities/os stepsdb.*são usados do ponto de vista de quem consome, não implementa, um provider.