Skip to content
Kumar Chandrachooda
.NET

From Clone to Chassis: The Onboarding Path

There is no dotnet new template and no CLI — Convey's onboarding is docs, samples and source. Reading the repo-of-repos layout and its packing scripts, then walking the actual path: build your first service from an empty project, one config section at a time.

By Kumar Chandrachooda 07 Jul 2026 6 min read
The path is the parts, in order

Every chapter so far has assumed you are already inside — reading source, running samples, writing tests. This one backs up to day zero, because the parent series never asked the most practical question a toolkit faces: how does a team actually start? Spring Boot has an initializer website. Modern .NET has dotnet new templates for everything. Convey, in the source tree I have been reading all along, has… none of that. No templates/ folder, no dotnet new convey, no scaffolding CLI. The onboarding path is three things: the documentation site (convey-stack.github.io), the samples, and the source itself.

I want to take that seriously rather than treat it as a missing feature, because the shape of the repository is itself half the onboarding story — and then walk the path I actually use to stand up a first service, which the samples demonstrate but never narrate.

The repo is a repo of repos

Clone Convey and look at src/. Every one of the thirty-two packages is not a bare project folder — it is a complete miniature repository: its own src/Convey.Whatever/ project, its own scripts/dotnet-pack.sh, its own packaging metadata. The umbrella repo stitches them together with one Convey.sln and a root scripts/pack.sh whose entire body is a loop:

for dir in src/*/
do
    dir=${dir%*/}
    echo Publishing NuGet package:  ${dir##*/}
    chmod +x ./$dir/scripts/dotnet-pack.sh
    exec ./$dir/scripts/dotnet-pack.sh &
    wait
done

(As shipped, this loop has a bash quirk worth smiling at: exec replaces the shell, so backgrounding it and wait-ing is doing more work than it needs to — the kind of script that grew, was never wrong enough to fix, and tells you CI happened elsewhere.)

The layout is a fossil of the project's history: these packages lived as separate GitHub repositories under the convey-stack organization, and the mono-repo checkout preserves each one's independence. That independence is a versioning statement — each package packs and ships on its own cadence, which is why your .csproj can reference Convey.MessageBrokers.RabbitMQ without dragging in Consul, Vault, or anything else from part 1's twenty-line composition. The granularity that makes Program.cs read like a menu is enforced here, at the packaging layer, one dotnet-pack.sh at a time.

For onboarding this has a concrete consequence: you adopt Convey a package at a time, and nothing forces the rest on you. The flip side is equally concrete: nothing coordinates versions for you either — no meta-package pins a known-good set, so your first afternoon includes picking package versions that agree with each other. Write them down in Directory.Packages.props; your successors will thank you.

What a template would have contained

The absence of scaffolding means every team writes its own “first service” doc eventually. Mine is below, and its structure is the real thesis of this chapter: onboard in the order the request flows, and keep every capability off until its infrastructure exists.

Step one: the walking skeleton. Empty web project, two packages (Convey, Convey.WebApi), and the smallest composition that boots:

services.AddConvey().AddWebApi().Build();
// ...
app.UseConvey().UseEndpoints(e => e.Get("ping", ctx => ctx.Response.WriteAsync("pong")));

Plus the config file's first section — "app": { "name": "Parcels Service" } — because the builder binds it immediately, and because the Figgle banner shouting your service's name at first run is, honestly, the toolkit's best onboarding moment. Notice which sample this skeleton resembles: Pricing, the eight-call service from chapter one. The samples are sized as onboarding stages — Pricing is step one, Deliveries is step two, Orders is the exam. Nothing in the repo says so, but use them that way.

Step two: the CQRS spine, no infrastructure. Add the three CQRS packages, one command, one handler, a dispatcher endpoint (Post<CreateParcel>("parcels")). Everything still runs on nothing — the dispatchers are in-memory (part 3). This is the stage where the team learns the marker interfaces and the handler shape, which is 80% of daily Convey work, with zero containers running.

Step three: first real dependency. AddMongo(), AddMongoRepository<Parcel, Guid>("parcels"), one container from the compose stack. One dependency, one section, verify, commit. Step four: the broker, and here the sequencing rule earns its keep — RabbitMQ's connection is eager, so this is the first step where a missing container means the service won't start; bring the container up before the package. Step five and beyond: observability (logger, then metrics, then Jaeger), then the optional fleet (Consul/Fabio/Vault) — added to code, left enabled: false in the config file until the environment that needs them exists.

The path's invariant: at every step, the diff is one or two packages, one config section, and one visible new behavior. That is the property a dotnet new template cannot give you — a template hands the team Orders' twenty-five-line composition on day one, and the twenty-five lines are exactly what a new team cannot yet debug when step four's container is missing. The chassis's own composability is the onboarding tool; the tragedy would be to skip it.

Copy-paste onboarding, and its two traps

Realistically, most teams onboard by copying Conveyor.Services.Orders wholesale — I have watched it happen three times. It works, with two traps this series has already armed you against. First, Orders is a teaching artifact: it ships both the controller and the dispatcher DSL for the same routes, and your copy should keep exactly one. Second, the config comes with the samples' dev defaults — guest/guest, tokens that read secret, logMessagePayload: true — which are correct in a sample and become findings the moment they reach any shared environment. A copied service needs the config audited, section by section; the chapter-two checklist is that audit.

And where the local repo offers no template, the wider ecosystem quietly does. DevMentors' Pacco reference system — a dozen-plus services all built on this chassis — is what “we copied a known-good service” looks like at scale, and its services are more production-shaped than the Conveyor teaching samples. That ecosystem, and when to study which repo, is the final chapter's whole subject.

What I would actually build

If I owned Convey onboarding for a team today, I would not write the CLI either. I would ship three things, in this order: a Directory.Packages.props pinning the blessed version set; an internal template repo that is essentially step-three-state (skeleton + CQRS + Mongo, everything else enabled: false with the sections pre-written); and a one-page path doc that says “delete what you don't need” — because deleting a config section teaches the section-per-package convention faster than any README. Templates generate structure; Convey's structure is so uniform that the sample is the template. The missing scaffolding turns out to be about four files, and the toolkit's discipline — one pattern, thirty-two times — is what makes those four files enough.

Next week, the last chapter: the ecosystem Convey grew out of and feeds into — DShop, Pacco, Ntrada, Chronicle, Pactify — and the close of both series.