Skip to content

Latest commit

 

History

History
145 lines (111 loc) · 8.65 KB

generating-a-solution.md

File metadata and controls

145 lines (111 loc) · 8.65 KB

Generating a solution

Install the template

dotskit does the same and also adds flags to services already generated, keeping the team's changes. This page is the template on its own.

The template lives in the template folder of this repository, next to its .template.config:

dotnet new install /path/to/DotNetSolutionKit/template

After pulling changes, install it again over the old one:

dotnet new install /path/to/DotNetSolutionKit/template --force

Generate

The first run generates the shared Common projects, the root solution file and the first service. Pass --Solution for it:

dotnet new DotNetSolutionKit -N MyCompany -P MyProduct -S Orders --Solution

Every further service is added from the solution's root:

dotnet new DotNetSolutionKit -N MyCompany -P MyProduct -S Billing

It generates the service folder and, after asking to run a command (--allow-scripts yes runs it without asking), adds the service's projects to All.sln, in a solution folder named after the last segment of the service's name. It leaves the files already in the solution as they are: Common gets nothing new, so a service takes only the flags whose part Common already has, and the database, the deployment and the names are passed again as the solution has them.

A service generated without --Solution leaves the root src/Directory.Packages.props alone. In a solution generated by this version of the template it already holds every version a service needs, with any -H and --Messaging; the flags that add files to Common are chosen once for the solution, see parameters. In an older solution the build stops with NU1010 and names the packages that have no version; copy their PackageVersion lines from the template's Directory.Packages.props and, for a flag, from its file in src/package-versions/.

Parameters

Parameter Default What it does
-N, --NamespaceRoot MyCompany Organization name, the root namespace. May be dotted.
-P, --ProductName Product Product name. May be dotted.
-S, --ServiceNameOrCustom Service Service name. May be dotted.
--Solution false Generates the solution: Common projects, All.sln and the root files, with the first service. Without it, only the service folder, into a solution already there. -M false of earlier versions still means the same.
--Database postgres Database: postgres or mssql (SQL Server). One value for the solution: pass it with --Solution and to every service.
-H, --Hangfire true Background jobs on Hangfire. Per service, also without --Solution.
--Messaging none Message bus: outbox or direct. Per service.
-I, --Infisical false Secrets from Infisical.
--Vault false Secrets from HashiCorp Vault.
--DiffApi false API contract diff on pull requests.
--GitHubCiCd false CI on GitHub Actions: build, tests on real servers, coverage on demand, secret scan. With --Solution: the workflows cover every service in All.sln.
-FF, --FeatureFlags false Feature flags.
--HierarchyRules false Access rules over a tenant tree.
--Storage false Object storage, S3-compatible.
-CH, --ClickHouse false ClickHouse: connections, schema check, readiness.
--MongoDB false MongoDB beside the main database: the client, the service's database, readiness.
--Notify none Channels of notifications: email - through SMTP or Graph API, with a sandbox outside Production; with --Messaging other services ask for it by a bus command.
--TestFramework nunit Test framework of the service's tests, nunit or xunit (v3); per service. Common's own tests stay on NUnit. See testing.
--Audit false Audit journal of entity changes; takes effect only with --Messaging outbox; without it the generation says it was not applied.
--ApiGateway false An API gateway on YARP in place of a service. Without --Solution only; with it the generation says it was not applied.
--Deploy compose Deployment files: compose, k8s or none. One value for the solution: pass the same to every service generated later.
--Agent claude Rules and skills for an AI agent: claude, opencode or none. With --Solution.
--HttpPort free port Port in launchSettings.json and on the host under compose; without it, a free port from 5000-5999 on the generating machine, so services generated one after another do not share a port.

-I, --Vault, --DiffApi, --FeatureFlags, --HierarchyRules, --Storage, --ClickHouse and --Audit add files to Common, and --MongoDB and --Notify add a project to src/capabilities, so they have to be passed with --Solution, when the shared code is generated. Pass -I, --Vault, --DiffApi, --FeatureFlags, --Storage, --ClickHouse, --MongoDB, --Notify and --Audit again to each service generated later that should use them: they change the service's code too, and the service then wires what Common and src/capabilities already have. A service generated without them leaves them out.

Dotted names

Any of -N, -P and -S may contain dots:

dotnet new DotNetSolutionKit -N Acme.Corp -P Shop.Online -S Sales.Orders --Solution

generates Acme.Corp.Shop.Online.Sales.Orders.API and the rest of the projects under that name. A dotted service name lets a domain be split into small services, such as Sales.Orders and Sales.Invoicing, instead of one shared project for the whole of Sales. A dotted product name keeps the origin in every namespace of a fork or a downloaded copy, where the repository name is gone.

Where a dot cannot go, the name is derived from the service name:

Where Form Example
Namespaces, projects, folders as given Acme.Corp.Shop.Online.Sales.Orders
C# identifiers without dots SalesOrdersDbContext
Database schema, Infisical folder, queue names lower case, dots replaced by underscores sales_orders

Name a service after its area, not after its main aggregate: -S Basket with a class Basket in it makes Basket both a namespace and a type, and C# refuses the type where the namespace is in scope (CS0118). Baskets, Shopping or Sales.Baskets avoid it.

Add the first migration

A generated service has a model and no migrations, so its database starts without tables. Add the first migration before the first run; the command needs no database:

cd src/services/MyCompany.MyProduct.Orders
dotnet ef migrations add Initial \
  -p MyCompany.MyProduct.Orders.Infrastructure \
  -s MyCompany.MyProduct.Orders.Infrastructure \
  -o EntityFramework/Migrations

The migration also creates the tables of the outbox, when the service was generated with --Messaging outbox. A service started without migrations logs a fatal line with this command, and every query to a missing table fails.

Configure and run locally

appsettings.json and appsettings.Local.json ship with the required values empty and a _comment_* key next to each one saying what goes there. Put your local values into appsettings.Secrets.json in the same folder. It is read only in the Local environment, the generated .gitignore keeps it out of the repository, and environment variables override it.

In other environments, pass the same keys as environment variables, for example ConnectionStrings__DefaultConnection.

The local launch profile sets ASPNETCORE_ENVIRONMENT=Local, so a plain dotnet run reads the secrets file:

dotnet run --project src/services/MyCompany.MyProduct.Orders/MyCompany.MyProduct.Orders.API

A misconfigured service stops at startup rather than at the first request; see startup checks.

To run the service before its database, broker or secret store exist, switch them off in configuration, for example Database__Enabled=false; see switching dependencies off.