ข้ามไปยังเนื้อหา

1 · Scaffold and run

เนื้อหานี้ยังไม่ได้แปลเป็นภาษาไทย แสดงเป็นภาษาอังกฤษแทน

In this step you generate the project, understand what the scaffold produced, and run the host. No code is written by you yet — but by the end you’ll know exactly what the CLI gave you and where everything lives.

From a working directory of your choice:

Terminal window
cephalon new Acme.Store --output ./Acme.Store
cd ./Acme.Store

The --output path becomes the solution root. Acme.Store.slnx is the modern solution file format used by .NET 10.

Acme.Store/
Acme.Store/
├── README.md # generated README explaining the layout
├── Acme.Store.slnx # solution file
├── Directory.Build.props # repo-wide MSBuild props
├── Directory.Build.targets # repo-wide MSBuild targets
├── Directory.Packages.props # centralised package versions
├── global.json # pins the SDK band
├── NuGet.config # points at .cephalon/packages first
7 collapsed lines
├── nuget.config # workaround for case-insensitive filesystems
├── Dockerfile # multi-stage Dockerfile
├── .dockerignore
├── compose.yaml # local docker compose
├── otel-collector-config.yaml # OTLP collector preset
├── .cephalon/
│ └── packages/ # local NuGet feed for Cephalon.* packages
├── deploy/ # 7 deployment targets
├── src/
│ ├── Acme.Store.Host/ # the executable
│ └── Acme.Store.Modules.Health/ # a single starter module
└── tests/
└── Acme.Store.Host.Tests/ # composition + behavior tests

A few non-obvious notes:

  • Directory.Packages.props uses centralised package management (CPM). Add new packages to that file, then reference them without version in individual .csproj files.
  • global.json pins to the SDK band — bump it deliberately when you upgrade.
  • The .cephalon/packages/ folder is part of the NuGet feed chain. The generated NuGet.config puts it first, so locally-staged packages override anything from the public feed.

Open the host entry point:

src/Acme.Store.Host/Program.cs
using Cephalon.AspNetCore;
using Cephalon.Engine;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Services
.AddCephalonAspNetCore()
.AddModulesFromAssemblies(typeof(Program).Assembly)
.Build(builder);
app.MapCephalon();
app.MapHealthChecks("/health");
app.Run();

Two things to notice:

  1. AddCephalonAspNetCore() registers the host adapter. It wires REST behavior support, OpenAPI/Scalar, the /engine/* routes, and the manifest endpoint.
  2. AddModulesFromAssemblies(...) asks the engine to discover modules from the listed assemblies. You can also load modules from explicit DLLs or package manifests — see Module authoring.
src/Acme.Store.Host/appsettings.json
{
"Engine": {
"Id": "acme-store",
"Data": { "IdStrategy": "Sfid", "Provider": null },
"Identity": { "Enabled": false },
"Tenancy": { "Enabled": false },
"Audit": { "Enabled": true },
"Messaging": { "Enabled": false },
"Transports": [ "RestApi" ]
},
"Logging": {
"LogLevel": { "Default": "Information" }
},
"AllowedHosts": "*"
}

The Engine:* section is read by the engine at build time. Each subsection is a capability:

  • Data:IdStrategy: Sfid — generated apps default to Sfid.Net sortable identifiers. You can switch to Guid, Long, or a custom strategy later.
  • Identity:Enabled: false — turning this on activates the identity capability and requires a provider package.
  • Tenancy:Enabled: false — same for multi-tenancy.
  • Audit:Enabled: true — audit is enabled by default; the Cephalon.Audit package handles recording.
  • Messaging:Enabled: false — eventing is off until you opt in (we’ll turn it on in step 5).
  • Transports: ["RestApi"] — REST is the only transport in this starter; we’ll add others later.
Terminal window
dotnet run --project ./src/Acme.Store.Host

The first run will restore packages. After a few seconds you should see:

dotnet run output
info: Cephalon.Engine[1001]
Cephalon Engine started: acme-store
modules: Acme.Store.Modules.Health (1.0.0)
transports: RestApi
capabilities: Audit
manifest schema: v2
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000

In a second shell:

Terminal window
curl http://localhost:5000/engine/manifest

You should see JSON with the engine id, the registered module, capabilities, and a manifest schema version. Try also:

Terminal window
curl http://localhost:5000/engine/runtime
curl http://localhost:5000/health
curl http://localhost:5000/scalar/v1

The last one opens the Scalar docs UI in the browser if you point at it — it’s the REST documentation surface the AspNetCore host wires automatically.

1.7 What the starter test project gives you

Section titled “1.7 What the starter test project gives you”

Open the composition smoke test:

tests/Acme.Store.Host.Tests/Architecture/CompositionSmokeTests.cs
public sealed class CompositionSmokeTests
{
[Fact]
public async Task Host_composes_successfully()
{
await using var host = await TestHostFactory.CreateAsync();
var manifest = host.Services.GetRequiredService<IRuntimeManifest>();
manifest.EngineId.Should().Be("acme-store");
manifest.Modules.Should().NotBeEmpty();
manifest.Modules.Should().Contain(m => m.Name == "Acme.Store.Modules.Health");
}
}

That single test fails the moment something stops composing. It is the canonical “did I break it?” check. Keep it green at every commit.

  • Acme.Store directory generated.
  • Host runs and prints the startup banner.
  • /engine/manifest returns JSON.
  • dotnet test ./tests/Acme.Store.Host.Tests is green.
  • The CLI prerelease flag. Forgetting --prerelease on cephalon new will pull a stable version that doesn’t exist yet. The new command itself doesn’t require it, but the install does.
  • Port already in use. Set ASPNETCORE_URLS=http://localhost:5050 if :5000 is taken.
  • Centralised package management. Don’t add Version= to .csproj lines. Add the package to Directory.Packages.props instead.