ronnieholm / FSharp-onion-architecture-sample

Applying F# principles to domain driven design/onion architecture.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

F# Scrum onion architecture sample

Got a comment or a question? Don't hesitate to drop me an email or open an issue.

This sample focuses on applying functional constructs over cluing together libraries and frameworks. It substitutes the .NET dependency injection container, FluentValidation, MediatR, Entity Framework, Moq, Respawn, and a migration tool for simpler constructs.

It's an example of imperative shell, functional core. Specifically, Program.fs and Infrastructure.fs make up the shell while Application.fs and Domain.fs make up the core.

Where F# shines is in the core and IntegrationTest.fs. The shell is similar in nature to many C# applications.

The application has the following features:

  • REST API adhering to the Zalando API guidelines with JWTs supporting role-based security.
  • A simple identity provider to issue, renew, and inspect JWTs accepted by the REST API.
  • Command Query Responsibility Segregation (CQRS) access to the application layer from clients.
  • Paged responses for endpoints which return collections.
  • Integration tests with the ability to fake any dependency.
  • Database migrations and initial data seeding.
  • ASP.NET health checks for memory and database.
  • k6 load test with baseline under tests/k6.
  • Architecture decision records under docs/architecture-decision-records.

The Scrum domain was chosen because it offers sufficient complexity and everyone is familiar with it, though most aspects of the application is illustrated with stories and tasks only.

With only stories and tasks, onion architecture may seem to introduce a disproportional amount of complexity. A larger domain and integrations with external services is where onion architecture starts to pays off.

That said, not every project requires an implementation of every concept from onion architecture and domain driven design. Those should be scaled up or down based on actual business complexity.

Getting started

Running the tests or the web app creates the SQLite databases in the Git root as scrum_web.sqlite and scrum_test.sqlite.

$ dotnet tool restore
$ dotnet build
$ dotnet test
$ dotnet run --project src/Scrum

Opening the Git repository with VSCode will make it pick up the DevContainer configuration.

Operations

# Authentication (supported roles: member and/or admin)
## Post
curl "https://localhost:5000/authentication/issue-token?userId=1&roles=member,admin" --insecure --request post
curl https://localhost:5000/authentication/renew-token --insecure --request post -H "Authorization: Bearer <token>"
curl https://localhost:5000/authentication/introspect --insecure --request post -H "Authorization: Bearer <token>"

# Stories
## Post
curl https://localhost:5000/stories --insecure --request post -H 'Content-Type: application/json' -H 'Authorization: Bearer <token>' -d '{"title": "title", "description": "description"}'
curl https://localhost:5000/stories/<storyId>/tasks --insecure --request post -H 'Content-Type: application/json' -H 'Authorization: Bearer <token>' -d '{"title": "title","description": "description"}'

## Put
curl https://localhost:5000/stories/<storyId> --insecure --request put -H 'Content-Type: application/json' -H 'Authorization: Bearer <token>' -d '{"title": "title1","description": "description1"}'
curl https://localhost:5000/stories/<storyId>/tasks/<taskId> --insecure --request put -H 'Content-Type: application/json' -H 'Authorization: Bearer <token>' -d '{"title": "title1","description": "description1"}'

## Delete
curl https://localhost:5000/stories/<storyId>/tasks/<taskId> --insecure --request delete -H 'Authorization: Bearer <token>'
curl https://localhost:5000/stories/<storyId> --insecure --request delete -H 'Authorization: Bearer <token>'

## Get
curl https://localhost:5000/stories/<storyId> --insecure -H 'Authorization: Bearer <token>'
curl "https://localhost:5000/stories?limit=<limit>&cursor=<cursor>" --insecure -H 'Authorization: Bearer <token>'

# PersistedDomainEvents
## Get
curl "https://localhost:5000/persisted-domain-events/<aggregateId>?limit=<limit>&cursor=<cursor>" --insecure -H 'Authorization: Bearer <token>'

# Health
## Get
curl https://localhost:5000/health --insecure

See also

About

Applying F# principles to domain driven design/onion architecture.

License:BSD 2-Clause "Simplified" License


Languages

Language:F# 97.6%Language:JavaScript 1.3%Language:Dockerfile 1.0%