Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A by the book DDD application with React/Redux and .NET Core. It features CQRS, event-sourcing, functional programming, TDD, Docker and much more.

License

NotificationsYou must be signed in to change notification settings

dnikolovv/cafe

Repository files navigation

Build Statuscodecov

logo

Café is an example application demonstrating a combination between domain-driven design and functional programming. It is continuously deployed tohttps://cafeapi.devadventures.net/openapi andhttps://cafe.devadventures.net. (if those are unavailable it's because I've stopped paying my Azure subscription, sorry)

Features

  1. DDD done by the book
  2. REST withHATEOAS
  3. CQRS
  4. Functional style command/query handlers
  5. Event-sourcing
  6. A complete integration tests suite (~100% coverage) + coverage reports
  7. Docker CI setup with multiple data sources + CD on Azure
  8. Real-time communications through SignalR

As a whole, this projects aims to stand out from the other examples by being as complete as possible code/structure wise. It should serve as an example of how a real enterprise project built using these principles would look like.

This project can also serve as an example for myIntegration Testing SignalR Websockets article. Seesome hub tests for examples.

What to note

1. Demonstrated DDD concepts

Bounded contexts

  • AuthContext
  • BaristaContext
  • CashierContext
  • ManagerContext
  • MenuContext
  • OrderContext
  • TabContext
  • TableContext
  • WaiterContext

SeeCafe.Business orCafe.Core for a more in-depth look.

Ubiqutous language

Menu - The café menu. Contains Menu Items that can be ordered either for a Tab or for a To-go Order.

Menu Item - An item from the menu, identified by a number.

Tab - An open bill on a table.

To-go order/Order - An order that is not linked to a particular table. Customers make these orders by going to the Cashier. When they get paid, the Cashier will issue them to the Barista for completion.

Customer - A person that opened a tab/made an order.

Table - Represents a physical table in the café. Tables are assigned to Waiters who are responsible for managing the tab.

Manager - The café manager. Manages the menu items. Manages the tables. Hires Waiters/Cashiers/Baristas. Assigns waiters to tables.

Cashier - Takes to-go orders.

Barista - Waits for confirmed to-go orders to complete them.

Waiter - Serves a fixed set of tables, takes orders and delivers/rejects menu items.

User/Account - An account with which you can login into the web portal. On its own, the account can do nothing, it must be assigned to some employee by the Admin. (e.g. you can link an account to a waiter)

Admin - The web portal administrator. Manages the links between the accounts and the employees. Has rights to do pretty much everything.

Event-sourcing

TheTab is an event-sourced aggregate that is constructed by the variousdomain events that are published by the command handlers using theEventBus.

Shared kernel

For when you need to share data between contexts, use the shared kernel.

SeeCafe.Core andCafe.Business

And probably a bit more if you care enough to look for them :)

2. Functional style command/query handlers

Each handler is implemented as a chain of functions (usingOptional.Async). Each function represents an operation that can either pass (continue the execution) or fail (return anError to the consumer).

Examples:

// OpenTabHandler.cspublicoverrideTask<Option<Unit,Error>>Handle(OpenTabcommand)=>TabShouldNotExist(command.Id).FlatMapAsync(tab=>TableShouldNotBeTaken(command.TableNumber).FlatMapAsync(tableNumber=>TheTableShouldHaveAWaiterAssigned(tableNumber).MapAsync(waiter=>PublishEvents(tab.Id,tab.OpenTab(command.CustomerName,waiter.ShortName,command.TableNumber)))));// OrderMenuItemsHandler.cspublicoverrideTask<Option<Unit,Error>>Handle(OrderMenuItemscommand)=>TabShouldNotBeClosed(command.TabId).FlatMapAsync(tab=>MenuItemsShouldExist(command.ItemNumbers).MapAsync(items=>PublishEvents(command.TabId,tab.OrderMenuItems(items))));// CallWaiterHandler.cspublicoverrideTask<Option<Unit,Error>>Handle(CallWaitercommand)=>TableShouldExist(command.TableNumber).FlatMapAsync(table=>TableShouldHaveAWaiterAssigned(table).MapAsync(waiter=>PublishEvents(table.Id,newWaiterCalled{TableNumber=table.Number,WaiterId=waiter.Id})));

--> See more handlers <--

The command/query validation is handled byFluentValidation and happens at the handler level rather than at the API level.

The chain itself contains all of the business validations such as checking whether the tab is closed, checking whether you're not serving beverages that haven't been ordered, etc.

Each handler is in a separate file to avoid the classes getting too big.

3. A complete integration tests suite

Most of the functionality is implemented using TDD, therefore the project has nearly 100% tests coverage, most of which are integration tests.

Examples:

[Theory][CustomizedAutoData]publicasyncTaskCanOpenTab(OpenTabopenTabCommand,HireWaiterhireWaiterCommand,AddTableaddTableCommand){// Arrangeawait_helper.SetupWaiterWithTable(hireWaiterCommand,addTableCommand);// Make sure we're trying to open a tab on the added tableopenTabCommand.TableNumber=addTableCommand.Number;// Actvarresult=await_fixture.SendAsync(openTabCommand);// Assertawait_helper.AssertTabExists(openTabCommand.Id,        t=>t.IsOpen==true&&t.WaiterName==hireWaiterCommand.ShortName&&t.CustomerName==openTabCommand.CustomerName);}[Theory][CustomizedAutoData]publicasyncTaskCanOpenTabOnARecentlyFreedTable(GuidtabId,inttableNumber){// Arrangeawait_helper.OpenTabOnTable(tabId,tableNumber);await_helper.CloseTab(tabId,1);varcommandToTest=newOpenTab{Id=Guid.NewGuid(),CustomerName="Customer",TableNumber=tableNumber};// Actvarresult=await_fixture.SendAsync(commandToTest);// Assertawait_helper.AssertTabExists(commandToTest.Id,        t=>t.IsOpen==true&&t.TableNumber==tableNumber);}[Theory][CustomizedAutoData]publicasyncTaskCanAddMenuItems(MenuItemView[]itemsToAdd){// Arrangevarcommand=newAddMenuItems{MenuItems=itemsToAdd};// Actvarresult=await_fixture.SendAsync(command);// AssertvaritemsInDb=await_fixture.ExecuteDbContextAsync(dbContext=>dbContext.MenuItems.ToListAsync());itemsInDb.ShouldAllBe(i=>itemsToAdd.Any(addedItem=>i.Number==addedItem.Number&&i.Description==addedItem.Description&&i.Price==addedItem.Price));}[Theory][CustomizedAutoData]publicasyncTaskCannotAddConflictingMenuItemsWhenAllAreConflicting(MenuItemView[]itemsToAdd){// Arrangevarcommand=newAddMenuItems{MenuItems=itemsToAdd};await_fixture.SendAsync(command);// Actvarresult=await_fixture.SendAsync(command);// Assertresult.ShouldHaveErrorOfType(ErrorType.Conflict);}

You can find out more by taking a look at thetests assembly.

4. Real-time communication using SignalR

Issue orders:

issue-order

Call waiter/ request bill:

call-waiter

Getting Started

These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.

Prerequisites

If you have Docker installed you can just double-clickrun-app.sh.

If not, you'll need to havePostgreSql either installed locally or at least have some instance available to set up the connection strings.

You'll also need at least version2.2 of the.NET Core SDK.

Running the API

Note that you can point both the event-store and the relational connection to the same database

Using Docker

  1. Executerun-app.sh. You should have a client running athttp://localhost:3000 and api athttp://localhost:5000

Using Visual Studio

  1. Open the.sln file using Visual Studio
  2. Set up the connection strings insideCafe.Api/appsettings.json
  3. ExecuteUpdate-Database inside thePackage Manager Console
  4. Run the application

Using thedotnet CLI

  1. Open the project folder inside your favorite editor
  2. Set up the connection strings insideCafe.Api/appsettings.json
  3. Executedotnet ef database update inside theCafe.Api folder
  4. Executedotnet run
  5. Go tohttp://localhost:5000 (or whatever port you're running it on)

Running the client

Using Docker

  1. Executerun-app.sh. You should have a client running athttp://localhost:3000 and API athttp://localhost:5000

Using npm

  1. While in the context of the./client folder
  2. Runnpm i
  3. Runnpm start

Running the tests

Note that you can point both the event-store and the relational connection to the same database

Using Docker

  1. Simply runrun-integration-tests.sh.

Using Visual Studio or thedotnet CLI

  1. Set up the connection strings insideBar.Tests/appsettings.json to a valid database. (if you point it to an unexisting one, the app will create it for you)
  2. Either run them through theTest Explorer in Visual Studio or usingdotnet test

Contributing

If you feel like contributing, PRs are welcome!

License

This project is licensed under the MIT License.

About

A by the book DDD application with React/Redux and .NET Core. It features CQRS, event-sourcing, functional programming, TDD, Docker and much more.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp