Receiver is a repository to receive meldinger / notifications / messages from different sources. The term 'meldinger' is by intention not translated to be consistent with naming conventions within the domain. The main source to receive meldinger from is Digdirs Altinn plattform, which is why this project is using several altinn related terms, also within the domain model.
The application exposes a scalar ui, which is locally accesible on http://localhost:9008/scalar/v1. Discover the available endpoints on the UI or use the OpenApi Spec. The OpenApi Spec can be imported by other tools like e.g. Postman.
Prerequisites:
- Docker installed
- dotnet SDK installed (v10)
- dotnet-ef tool (dotnet tool install --global dotnet-ef)
To test and debug locally, you need to run the following commands:
Run
dotnet/dotnet efcommands from thesrc/directory (the solutionMeldingerReceiver.slnxlives there). Rundocker composeandpnpmcommands from the repository root.
docker compose -f compose.infra.yaml --profile monitoring up -d
The monitoring profile is optional and can be dropped. If used, a telemetry backend as defined in the opentelemetry section is spinned up.
This starts (as per today) a plain postgres instance without any seed, a common valkey service as well as en emulator for the managed google cloud storage service.
Before starting the actual application, we need to create quartz tables for persisting scheduled jobs (this also needs to be done whenever you have changed something withing the entity model, just remember to adjust the migration name):
dotnet ef migrations add InitDb --startup-project App/src --project Infrastructure/src -o Db/Migrations
Now, start the actual asp dotnet core application:
dotnet run --project App/src
Prerequisites:
- Docker installed
- dotnet ef migrations have to be up to date / migration files need to be available (see previous section)
Start the application by running:
docker compose up --build -d
If you want to stop all containers, run:
docker compose down
If you want to get a clean database at the next startup, simply remove all the attached volumes:
docker compose down -v
- Spin up altinns app localtest environment together with an altinn app you want to test
- Start this application as described in #local-development-for-developers
- Send a post request to 'http://localhost:9008/subscriptions' via postman or use scalars UI
- Start the altinn application 'http://local.altinn.cloud/dat/{appId}' and submit a new form
In order to ensure that the intended architecture is maintained, there are some basic ArchUnit.Tests (docs) which will ensure that the dependencies do not cross the boundaries established by hexagonal architecture. Enhance the ArchUnit tests with other guidelines you want to maintain, like Namespaces, Naming Conventions and so on.
Also, a couple of sample tests in the different modules are provided to show how things can be tested. In order to run the repository tests, a running docker instance is required. (E.g. Docker Desktop)
dotnet test
.
└── src
├── App
│ ├── src
│ └── test
├── ArchUnit.Tests
├── Domain
│ ├── Data
│ ├── Logic
│ │ ├── src
│ │ └── test
│ └── Ports
│ ├── App
│ └── Infrastructure
├── Infrastructure
│ ├── src
│ └── test
├── Publish
│ └── Receiver.Publish
├── Tools
│ └── Tools.GenerateOpenApi
├── generated
└── npm_publish- ArchUnit.Tests
- Important tests to gurantee the below structure keeps maintained
- Domain
- Domain.Logic implements Domain.Ports.App and uses Domain.Ports.Infrastructure
- Domain.Logic.Test contains tests that validate the domain logic
- Domain.Data contains Classes/DTOs which can be shared across layers
- Infrastructure (Outgoing: infrastructure the application talks with)
- Infrastructure implements Domain.Ports.Infrastructure (i.e. Adapters)
- Infrastructure.Test contains tests that validate the Infrastructure
- App (Incoming: adapters to make it possible to talk with the application)
- App uses Domain.Ports.App
- Responsible for injecting the necessary dependencies and exposing API endpoints
- App.Test contains typically integration tests
- Tools
- Various tools that are used for development, e.g. OpenAPI generation
Domain.Logic and Infrastructure implementations are internal, and only exposed through DependencyInjection extensions.
We want to use structured Logging in order to read, filter and query logs in an easy manner. See Logging in C# and .NET.
That means, if you are not in the API Adapter Layer (or not where you start the application), you can simply inject a logger Instance via the constructor of a class (like ILogger<ExampleClass> logger). Feel free to write logging extension methods in order to streamline your logging even more.
In all places where you can`t use DependencyInjection, a logger instance can be created the following way:
ILogger<Function> logger = LoggerFactory.Create(builder => builder.AddConsole().AddJsonConsole()).CreateLogger<Function>();To ensure that our logging stays consistent, there are ArchUnit tests which check if other logging mechanisms are used and which will fail in that case.
General information about observability
We strongly recommend to use the default opentelemetry setup in this application. It ensures a minimal level of observability and can be fine-tuned if needed. When running docker-compose with the monitoring profile, a default backend of a opentelemetry-collector, mimir/prometheus (Metrics), loki (Logs), tempo (Traces) and grafana (Dashboard) is started. You will find the same or a similar setup in our production environment. To explore the telemetry data your application is producing, check out Grafana Explore.
Checkout OpenTelemetry .NET Traces for best practices, fine-tuning and examples.
services.AddOpenTelemetry()
.WithTracing(options =>
{
options.AddAspNetCoreInstrumentation();
options.AddHttpClientInstrumentation();
options.AddEntityFrameworkCoreInstrumentation();
options.AddNpgsql();
options.AddOtlpExporter();
})Checkout OpenTelemetry .NET Logs for best practices, fine-tuning and examples.
services.AddOpenTelemetry()
.WithLogging(
logging => logging.AddOtlpExporter(),
options => options.IncludeFormattedMessage = true
);Checkout OpenTelemetry .NET Metrics for best practices, fine-tuning and examples.
services.AddOpenTelemetry()
.WithMetrics(options =>
{
options.AddAspNetCoreInstrumentation();
options.AddHttpClientInstrumentation();
options.AddOtlpExporter();
});This template includes automated TypeScript type generation from your OpenAPI specification. The generated types can be used to create clients or for type-safe interactions with the API.
- Type generation tooling is configured in package.json.
- Customize the OpenAPI specification in App/src/Extensions/StartupExtensions.cs, where
ConfigureApi()callsConfigureBasicOpenApiSpec(...). - Adjust TypeScript generation options in package.json. Update the
generate:tsscript to customize openapi-typescript behavior as needed.
Generate types on-demand:
pnpm install # Run this once to install dependencies
pnpm generate:types
This will:
- Generate the OpenAPI specification in
src/generated/openApi.json - Generate TypeScript type definitions in
src/generated/types.d.ts
The publishable npm package root is npm_publish. It is published alongside receiver releases and uses the version from Publish/Receiver.Publish/Receiver.Publish.csproj, so the committed npm package version is only a placeholder and should not be bumped manually.
Prerelease receiver versions (*-*) are published with the next npm tag. Stable receiver versions use npm's default tag.
If you have dependent steps, such as package publishing, ensure that the generated types are up to date:
pnpm generate:types && git diff --exit-code
Medium: Hexagonal Architecture, there are always two sides to every story