Beta: in progress. These pages describe what the next release is getting, from releases/r10.0-develop, and may change before it ships. For the version you can run today, read the stable documentation.
Get started · Beta
Architecture
Components, projects, adapter runtimes, data, messaging, storage and caching
One service#
Bitween runs as a single ASP.NET Core process built from SW.Bitween.Web. Every replica does all of the following.
| Role | How |
|---|---|
| Serves the admin UI | Static files from wwwroot, falling back to index.html for client-side routes |
Serves the REST API under /api |
Handler classes in SW.Bitween.Api/Resources, exposed by the SimplyWorks CqApi library |
| Serves partner gateways | GatewayController, at /api/gateway/{urlName}/sync and /async |
| Serves Swagger UI and health | /swagger, with the spec at /api/swagger.json, and /health |
| Consumes RabbitMQ | Exchange processing, result notifications, bus front doors and alerts |
| Runs scheduled jobs | Quartz, with its job store in Bitween's own database |
| Holds data source connections | Resident adapters under a supervisor, on nodes with Bitween:BusProvidersEnabled |
flowchart TB
subgraph Host[SW.Bitween.Web process]
UI[Admin UI]
API[CqApi handlers]
GC[GatewayController]
XS[XchangeService consumer]
BS[BusService consumer]
RA[RetryAlertService consumer]
Q[Quartz jobs]
NA[Native adapters]
SL[Serverless runner]
end
DB[(Database)]
MQ[(RabbitMQ)]
ST[(Object storage)]
UI --> API
API --> DB
GC --> DB
DB -->|events after commit| MQ
MQ --> XS & BS & RA
XS --> NA & SL
XS --> ST
Q --> DB
SL -->|adapter packages| STProjects#
| Project | Role | References |
|---|---|---|
SW.Bitween.Web |
Host, startup wiring, authentication, headers, admin UI | Api, PgSql, MySql, MsSql, NativeAdapters |
SW.Bitween.Api |
Domain, BitweenDbContext, API handlers, pipeline, jobs, settings, the adapter editor's server side |
NativeAdapters, Sdk, Adapters.Tooling |
SW.Bitween.NativeAdapters |
Built-in adapters and the rules-based mapper | none |
SW.Bitween.Sdk |
Request and response models, JSON converters, retry policy evaluator | none |
SW.Bitween.Adapters |
The Bitween adapter contract: its JSON in Contract/ and its .NET form (IBitweenHandler, IBitweenMapper, IBitweenValidator, IBitweenReceiver, ExchangeFile, ValidationResult). Targets netstandard2.0 with no dependencies; published to NuGet as SimplyWorks.Bitween.Adapters |
none |
SW.Bitween.Adapters.Tooling |
Bitween's adapters on SW-Serverless's tooling (SimplyWorks.Serverless.Tooling): the contract, the templates for the four kinds, and the Python and Node packages to vendor into builds, embedded from SW.Bitween.Adapters/Contract and sdk/ |
none |
SW.Bitween.Cli |
The bitween command. See The bitween CLI |
Adapters.Tooling |
SW.Bitween.PgSql, MySql, MsSql |
Provider contexts, migrations, design-time factories | Api |
SW.Bitween.Adapters.Bus.RabbitMq, Bus.Sqs |
Resident broker adapters | none |
SW.Bitween.Adapters.Db.Core, Db.PostgreSql, Db.MySql, Db.SqlServer, Db.Oracle |
Resident database adapters and their shared base | Db.Core |
SW.Bitween.Sample* |
Example custom adapters, including a resident handler | none |
SW.Bitween.UnitTests, IntegrationTests, HttpTests, TransportTests |
Test suites. See Development | The projects they test; HttpTests hosts Web and runs the CLI |
Outside the solution, sdk/python is the Python package simplyworks-bitween and sdk/node the Node package @simplyworks/bitween: the four kinds for adapters in those languages, built on SW-Serverless's sw-serverless and @simplyworks/sw-serverless.
Bitween's own projects target .NET 10, the resident adapters included, except SW.Bitween.Adapters, which targets netstandard2.0 so that an adapter on any supported .NET can reference it. The resident adapters share their settings attributes from SW.Bitween.Adapters.Shared as linked source.
SimplyWorks libraries#
Much of the plumbing comes from Simplify9's SimplyWorks.* packages.
| Package | Provides |
|---|---|
SimplyWorks.CqApi |
Turns handler classes into HTTP endpoints, with Swagger |
SimplyWorks.Bus, SimplyWorks.Bus.RabbitMqExtensions |
RabbitMQ publish and consume, retry and dead-letter queues, management API readers |
SimplyWorks.CloudFiles.* |
Object storage for S3, Azure Blob, Oracle Cloud and local disk |
SimplyWorks.Serverless |
Downloads adapter packages and runs them, either as one process per call or as long-lived resident processes, on .NET, python3 or node |
SimplyWorks.Serverless.Tooling |
Building, checking, running and publishing adapter packages: what sw-serverless, the bitween CLI and the adapter editor use |
SimplyWorks.Scheduler.* |
Quartz with a persistent job store per database provider, plus execution history |
SimplyWorks.EfCoreExtensions |
Startup migration, JSON columns, audit stamping |
SimplyWorks.Logger.* |
Console and Elasticsearch logging |
SimplyWorks.PrimitiveTypes |
Adapter interfaces, XchangeFile, request context, domain events |
SimplyWorks.HttpExtensions |
JWT helpers and the typed API client base |
Data#
Bitween keeps configuration and runtime records in one relational database, through Entity Framework Core 9.
- Providers.
Bitween:DatabaseTypeselects PostgreSQL, SQL Server or MySQL. Each provider has its own context and migrations project. On PostgreSQL, tables live in theinfolinkschema and columns use snake_case. - Migrations run automatically at startup, before the host starts serving.
- Quartz tables live in the same database.
- Audit. Saving a configuration entity writes audit rows in the same transaction. So does saving an adapter draft, a version published, promoted or withdrawn from Bitween, and each read of an adapter's source. Runtime rows such as exchanges are not audited. The synchronous
SaveChangesthrows, so no save can skip the audit. - Domain events are published to RabbitMQ after the transaction commits.
| Area | Tables |
|---|---|
| Configuration | Documents, Partners, PartnerApiCredentials, Subscriptions, SubscriptionSchedules, SubscriptionCategories, WorkGroups, GlobalAdapterValuesSets, ApiGateways, ApiGatewayPartners, BusGateways, BusGatewayRoutes, RetryPolicies, RetryAlertOverrides, NotificationChannels, Notifiers, Settings, DataSources, DataSourceStatements |
| Runtime | Xchanges, XchangeResults, XchangePromotedProperties, XchangeAggregations, XchangeNotifications, OnHoldXchanges, DelayedRetries, RetryGroupUsages, ReceiveAttempts, InboundMessages, AdapterStates, ClusterLeases, ClusterNodes |
| Adapters | AdapterDrafts, AdapterReleases, AdapterSourceAccesses |
| Identity | Accounts, Roles, AccountRoles, RefreshTokens |
| Audit | AuditEntries |
Messaging#
RabbitMQ carries all asynchronous work. Queue names are lower-cased and built from the bus's process exchange, Bitween:QueuePrefix, the consumer class and the message name. The bus library adds a .retry and a .bad queue next to each one.
| Lane | Consumer | Message name | Carries |
|---|---|---|---|
| Work | XchangeService |
{workGroupId}{busMessageName}, or 0Ungrouped |
Exchange ids to process |
| Notifications | XchangeService |
The work lane's name plus -Result |
Result ids for notifications |
| Front door | BusService |
Each bus-enabled information type's message type name | Documents from other systems |
| Control | XchangeService |
SubscriptionUnpausedEvent |
Releasing on-hold exchanges |
| Control | RetryAlertService |
RetryBudgetExhaustedEvent |
Budget alerts |
| Legacy | XchangeService |
ApiXchangeCreatedEvent and four similar names |
Only while their queues, left by a version before work groups, still hold something. Bitween looks for them at each start and removes them once empty |
Creating a work group or a bus-enabled information type asks every instance to refresh its consumers, so new queues are picked up without a restart. Queues left behind by deleted or renamed lanes appear on the Queue health page.
Adapter runtimes and data sources#
Every stage of the pipeline runs its adapter through one invoker, which asks three runtimes in turn.
| Runtime | Runs |
|---|---|
| Native | Adapters compiled into Bitween, in process |
| Resident | Long-lived adapter processes that keep a connection or pool open |
| Classic | Custom adapters as a new process for each call |
A data source is a broker or database connection held by a resident adapter. On nodes with Bitween:BusProvidersEnabled, a supervisor reconciles every 30 seconds: it takes leases, starts and restarts adapters, and writes their health back. Broker connections run on one node at a time, held through an exclusive queue on Bitween's RabbitMQ and fenced by a counter in ClusterLeases. Database pools run on every enabled node. See Data sources.
Storage#
Exchange files are written as private text objects under this key.
{prefix}/{exchangeId}/{input|output|response}
{prefix} is Bitween:DocumentPrefix as it was when the exchange was created, recorded on the exchange (FilesPrefix), so the setting can change without losing files already written. Exchanges from before that column use the prefix recorded the first time this version started, kept in the Settings table as Bitween.LegacyDocumentPrefix.
The default prefix is temp30/Bitweendocs. The storage libraries keep delete rules for temp1/, temp7/, temp30/ and temp365/ on the bucket, so the default keeps files for 30 days. They're read back through ICloudFilesLifecycle for the Settings page and to explain a missing file.
Every file is written, and storage is the record of every payload; the database holds none. Writes run beside the work they belong to, and the save of the exchange or result that names a file waits for its write, so a saved row always has its files. On the way through, a payload no larger than Bitween:MessagePayloadMaxBytes (256 KB) also travels with the bus message: the input with the exchange's created message, and the response with the signal to a waiting sync call. Those are copies for the trip, used instead of a storage read; a message without one, such as one the outbox publishes again, is served from storage.
Readers without a Bitween login get links Bitween serves itself: an aggregation's handler, a partner reading its exchange result, an API client reading the exchange list. A link is {address}/api/files/{seal}/{storage key}, where the address is the public address the chart sets from its host name, else the one the request came in on, else (a scheduled job) the instance's own, such as http://localhost:8080. The seal is an HMAC of the storage key with a key derived from Token:Key, so a link opens its own file and no other, and nothing is stored per link. Custom adapter packages live under Bitween:AdapterPath.
Caching#
Information types, active subscriptions, notification channels, legacy notifiers, work groups, global value sets and bus gateway routes are cached in memory for 10 minutes. Every change made through the API broadcasts a revoke over the bus, and each instance then drops its cache and reloads settings. If the broadcast fails, the change is still saved, but other instances can serve old values until their cache expires.
Inactive subscriptions are not cached, so no entry point can start them.
Running more than one replica#
Replicas share the database and RabbitMQ. Exchange processing scales out by adding replicas and tuning prefetch and parallel exchanges per work group. These points need care.
- Quartz runs clustered on every database provider: each replica gets its own scheduler instance id, and a fire is claimed through row locks in the Quartz tables, so each fire runs on one replica only. Which replica runs a job is up to Quartz, and one replica often runs them all. If it stops, another picks the jobs up within about 30 seconds, Quartz's idle wait; a replica that dies without stopping misses its 10-second check-ins, and about 20 seconds later the others release what it had claimed. Retry, retention, cleanup, pruning and queue snapshot jobs each have one schedule, run one at a time across the cluster, and need nothing more.
- Each schedule of a receiver or aggregation, and each Receive now or Aggregate now, is a separate Quartz job, so two runs of one subscription can overlap, on one replica or two. Both claim the subscription's database running flag first, and the second one skips.
- Settings and cache changes reach other replicas only through the bus broadcast.
- Data source adapters run only on replicas with
Bitween:BusProvidersEnabled. A delivery through a data source fails on a replica without it.