Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 76 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ When a todo is completed, if this is the first completed todo, an email should b
* **Authentication**
* **Authorization** (Even at the repository level)
* **Automatic JWT renewal**
* **gRPC query caching**
* **Automatic client code generation using gRPC**
* **gRPC query caching** (deprecated)
* **Automatic client code generation using OpenAPI**

## Technologies Used - Overview
Here are listed some of the specific technologies used for the implementation of the project:
* **Authentication**: [JSON Web Tokens - JWT](https://jwt.io/)
* **Databases - Persistence**: [MongoDB](https://www.mongodb.com/), [PostgeSQL](https://www.postgresql.org/)
* **Testing**: [JEST](https://jestjs.io/)
* **External Communication Protocols**: [REST](https://en.wikipedia.org/wiki/Representational_state_transfer), [gRPC](https://grpc.io/)
* **External Communication Protocols**: [REST](https://en.wikipedia.org/wiki/Representational_state_transfer), [gRPC](https://grpc.io/) (deprecated)
* **Frameworks**: [ΝestJS](https://nestjs.com/)
* **PubSub technology**: [NATS](https://nats.io/)
* **Message Streaming Technology**: [JetStream](https://docs.nats.io/nats-concepts/jetstream) *by NATS*
Expand Down Expand Up @@ -120,14 +120,14 @@ docker compose -p bitloops-todo-app up -d
```
from the terminal inside the project **in order to download and run the necessary containers**.

Then the ReactJS front-end application will be visible at: `http://localhost:3000`.
Then the ReactJS front-end application will be visible at: `http://localhost:4173`.

<p align="center" style="margin-bottom: 0px !important;">
<img width="400" alt="image" src="https://github.com/bitloops/ddd-hexagonal-cqrs-es-eda/assets/1571105/4570473b-4e67-4050-9935-967acfe0b7c6" alt="Frontend application" align="center">
</p>

<p align="center">
Frontend React JS application (new version using MVVM!)
Frontend React JS application (new version using Vite and MVVM!)
</p>

# IV. Design Process and Decisions
Expand Down Expand Up @@ -241,7 +241,7 @@ You may find tutorials on how to use **Postman** for REST and gRPC requests belo
* REST ([link](https://hevodata.com/learn/postman-rest-client/))
* gRPC ([link](https://learning.postman.com/docs/sending-requests/grpc/first-grpc-request/))

To just test the app is app and running you can just invoke `http://localhost:8082` URI with Post request as shown in the picture below:
To just test the app is app and running you can just invoke `http://localhost:8080` URI with Post request as shown in the picture below:

<p align="center" style="margin-bottom: 0px !important;">
<img width="900" src="https://storage.googleapis.com/bitloops-github-assets/app-testing-confirmation.png" alt="App Running Confirmation" align="center">
Expand All @@ -258,7 +258,7 @@ A faster way to test the app works is to use **[cURL](https://curl.se/)**. In mo

To just test the app is app and running you can just run the following command on terminal:

```curl http://localhost:8082/```
```curl http://localhost:8080/```

The server should respond (in the terminal) with:
```{"statusCode":404,"message":"Cannot GET /auth/register","error":"Not Found"}```
Expand Down Expand Up @@ -420,28 +420,76 @@ Below is a summary of all the software architecture and design patterns used in

## Table of Contents

- [Software Architecture](#software-architecture)
- [Layered Architecture](#layered-architecture)
- [Separation of concerns benefits example](#separation-of-concerns-benefits-example)
- [Limitations of the classical layered architecture](#limitations-of-the-classical-layered-architecture)
- [Modern Layered Architectures](#modern-layered-architectures)
- [The Anti-pattern (beware)](#the-anti-pattern-beware)
- [Hexagonal Architecture (or Clean / Onion Architecture)](#hexagonal-architecture-or-clean--onion-architecture)
- [Ports And Adapters](#ports-and-adapters)
- [Driven Adapters vs Driving Adapters](#driven-adapters-vs-driving-adapters)
- [Inversion of Control](#inversion-of-control)
- [Domain Driven Design (DDD)](#domain-driven-design-ddd)
- [Key advantages of using DDD](#key-advantages-of-using-ddd)
- [Strategic and Tactical DDD](#strategic-and-tactical-ddd)
- [ddd-hexagonal-cqrs-es-eda](#ddd-hexagonal-cqrs-es-eda)
- [Table of Contents](#table-of-contents)
- [I. Introduction](#i-introduction)
- [Overview](#overview)
- [Todo application business requirements](#todo-application-business-requirements)
- [II. Technologies and Technical Features](#ii-technologies-and-technical-features)
- [Technical Features](#technical-features)
- [Technologies Used - Overview](#technologies-used---overview)
- [III. Quick start - running the ToDo App](#iii-quick-start---running-the-todo-app)
- [Prerequisites](#prerequisites)
- [Running the app](#running-the-app)
- [IV. Design Process and Decisions](#iv-design-process-and-decisions)
- [Design Process - Event Storming](#design-process---event-storming)
- [Design Decisions](#design-decisions)
- [V. Running in development mode](#v-running-in-development-mode)
- [A. Project Setup](#a-project-setup)
- [Prerequisites](#prerequisites-1)
- [Running the app](#running-the-app-1)
- [B. Application Validation](#b-application-validation)
- [Test the application is running](#test-the-application-is-running)
- [Postman](#postman)
- [cURL (only for initial testing)](#curl-only-for-initial-testing)
- [Running the application tests](#running-the-application-tests)
- [C. Understanding the project structure](#c-understanding-the-project-structure)
- [API Folder](#api-folder)
- [Bounded-Contexts Folder](#bounded-contexts-folder)
- [Config Folder](#config-folder)
- [Lib Folder](#lib-folder)
- [Module Structure](#module-structure)
- [Application Folder](#application-folder)
- [Commands folder](#commands-folder)
- [Queries folder](#queries-folder)
- [Domain folder](#domain-folder)
- [Contracts folder](#contracts-folder)
- [Ports folder](#ports-folder)
- [Tests folder](#tests-folder)
- [proto folder](#proto-folder)
- [VI. Conclusion](#vi-conclusion)
- [❓ Questions](#-questions)
- [📚 Theoretical Review](#-theoretical-review)
- [Table of Contents](#table-of-contents-1)
- [Software Architecture](#software-architecture)
- [Layered Architecture](#layered-architecture)
- [Separation of concerns benefits example](#separation-of-concerns-benefits-example)
- [Limitations of the classical layered architecture](#limitations-of-the-classical-layered-architecture)
- [Modern Layered Architectures](#modern-layered-architectures)
- [The Anti-pattern (beware)](#the-anti-pattern-beware)
- [Hexagonal Architecture (or Clean / Onion Architecture)](#hexagonal-architecture-or-clean--onion-architecture)
- [Ports And Adapters](#ports-and-adapters)
- [Driven Adapters vs Driving Adapters](#driven-adapters-vs-driving-adapters)
- [Inversion of Control](#inversion-of-control)
- [Domain Driven Design (DDD)](#domain-driven-design-ddd)
- [Key advantages of using DDD](#key-advantages-of-using-ddd)
- [Strategic and Tactical DDD](#strategic-and-tactical-ddd)
- [Strategic: Building a Domain Model](#strategic-building-a-domain-model)
- [Tactically: Implementing DDD](#tactically-implementing-ddd)
- [DDD \& Hexagonal Architecture are Complementary](#ddd--hexagonal-architecture-are-complementary)
- [Behavior Driven Development (BDD)](#behavior-driven-development-bdd)
- [Event-Driven Architecture](#event-driven-architecture)
- [Command and Query Responsibility Segregation (CQRS)](#command-and-query-responsibility-segregation-cqrs)
- [Event Sourcing (ES)](#event-sourcing-es)
- [Eventual Consistency](#eventual-consistency)
- [Event Storming](#event-storming)
- [🚀 Bringing this all together!](#-bringing-this-all-together)
- [🙌 Contributing](#-contributing)
- [Behavior Driven Development (BDD)](#behavior-driven-development-bdd)
- [Event-Driven Architecture](#event-driven-architecture)
- [Command and Query Responsibility Segregation (CQRS)](#command-and-query-responsibility-segregation-cqrs)
- [Event Sourcing (ES)](#event-sourcing-es)
- [Eventual Consistency](#eventual-consistency)
- [Event Storming](#event-storming)
- [Big Picture Event Storming](#big-picture-event-storming)
- [Process Level Event Storming](#process-level-event-storming)
- [Design Level Event Storming](#design-level-event-storming)
- [Event Storming Syntax](#event-storming-syntax)
- [More on Event Storming](#more-on-event-storming)
- [🚀 Bringing this all together!](#-bringing-this-all-together)
- [🙌 Contributing](#-contributing)
- [👨‍💻 Additional learning resources](#-additional-learning-resources)
- [Articles](#articles)
- [Blogs](#blogs)
Expand Down
6 changes: 3 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ ENV NATS_HOST bl-nats
ENV GRAFANA_ADMIN_USER admin
ENV GRAFANA_ADMIN_PASSWORD admin
ENV NODE_ENV production
ENV AUTH_URL "http://todo-backend:8082/auth/login"
ENV AUTH_URL "http://todo-backend:8080/auth/login"
ENV PROXY_URL "http://todo-backend:8080"
ENV REGISTRATION_URL "http://todo-backend:8082/auth/register"
ENV REGISTRATION_URL "http://todo-backend:8080/auth/register"
# Expose the port on which the app will be running (3000 is the default that `serve` uses)
EXPOSE 8081 8082
EXPOSE 8080
# Start the app
CMD [ "yarn", "start:prod" ]
3 changes: 1 addition & 2 deletions backend/frontend-development.docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ services:
build:
context: .
ports:
- '8081:8081'
- '8082:8082'
- '8080:8080'
networks:
- bitloops

Expand Down
42 changes: 26 additions & 16 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "todo",
"version": "0.1.3",
"version": "0.2.0",
"description": "Todo Backend using Domain Driven Design (DDD), Hexagonal Architecture, CQRS, Event Sourcing (ES), Event Driven Architecture (EDA), Behaviour Driven Development (BDD) using TypeScript and NestJS. Like what you see? Don't forget to star! ⭐",
"author": "Bitloops S.A.",
"private": false,
Expand All @@ -27,31 +27,41 @@
},
"dependencies": {
"@bitloops/bl-boilerplate-core": "^0.3.6",
"@bitloops/bl-boilerplate-infra-mongo": "^0.1.2",
"@bitloops/bl-boilerplate-infra-nest-auth-passport": "^0.1.4",
"@bitloops/bl-boilerplate-infra-nest-jetstream": "^0.0.8",
"@bitloops/bl-boilerplate-infra-postgres": "^0.1.1",
"@bitloops/bl-boilerplate-infra-telemetry": "^0.1.3",
"@fastify/autoload": "^6.3.1",
"@fastify/cors": "^11.0.1",
"@fastify/helmet": "^13.0.1",
"@fastify/swagger": "^9.5.1",
"@fastify/swagger-ui": "^5.2.3",
"@grpc/grpc-js": "^1.8.13",
"@nestjs/common": "^9.4.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.0.0",
"@nestjs/microservices": "^9.3.10",
"@nestjs/platform-fastify": "^9.4.0",
"@nestjs/cli": "^11.0.7",
"@nestjs/common": "^11.1.3",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.3",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/microservices": "^11.1.3",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-fastify": "^11.1.3",
"@nestjs/schematics": "^11.0.5",
"@nestjs/swagger": "^11.2.0",
"@nestjs/testing": "^11.1.3",
"async-mutex": "^0.5.0",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"fastify": "^5.4.0",
"google-protobuf": "^3.21.2",
"jsonwebtoken": "^9.0.0",
"mongodb": "^6.3.0",
"nats": "^2.13.1",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.16.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/google-protobuf": "^3.15.6",
"@types/jest": "29.5.12",
"@types/jsonwebtoken": "^9.0.1",
Expand All @@ -62,8 +72,8 @@
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"grpc-tools": "^1.12.4",
"grpc_tools_node_protoc_ts": "^5.3.3",
"grpc-tools": "^1.12.4",
"jest": "29.7.0",
"passport": "^0.6.0",
"prettier": "^2.3.2",
Expand All @@ -74,7 +84,7 @@
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.2.0",
"typescript": "4.9.5"
"typescript": "^5.8.3"
},
"jest": {
"moduleFileExtensions": [
Expand Down
13 changes: 7 additions & 6 deletions backend/src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthController } from './authentication.controller';
import { TodoController } from './todo.rest.controller';
import { TodoGrpcController } from './todo.grpc.controller';
import { TodoSSEController } from './todo.sse.controller';
import {
JetstreamModule,
NatsStreamingIntegrationEventBus,
NatsStreamingMessageBus,
} from '@bitloops/bl-boilerplate-infra-nest-jetstream';
} from '@lib/infra/nest-jetstream';
import configuration from '@src/config/configuration';
import authConfiguration, {
AuthEnvironmentVariables,
} from '@src/config/auth.configuration';
import { AuthModule } from '@bitloops/bl-boilerplate-infra-nest-auth-passport';
import { AuthModule } from '@lib/infra/nest-auth-passport';
import {
// CorrelationIdMiddleware,
TracingModule,
} from '@bitloops/bl-boilerplate-infra-telemetry';
} from '@lib/infra/telemetry';
import { SSEModule } from './sse.module';

@Module({
imports: [
Expand Down Expand Up @@ -60,12 +61,12 @@ import {
}`,
],
}),

SSEModule,
TracingModule.register({
messageBus: NatsStreamingMessageBus,
}),
],
controllers: [AuthController, TodoController, TodoGrpcController],
controllers: [AuthController, TodoController, TodoSSEController],
})
// implements NestModule
export class ApiModule {
Expand Down
18 changes: 9 additions & 9 deletions backend/src/api/authentication.controller.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import {
Body,
Controller,
Get,
Post,
Request,
UseGuards,
Inject,
HttpStatus,
HttpException,
Patch,
} from '@nestjs/common';
import { ChangeEmailCommand } from '@src/lib/bounded-contexts/iam/authentication/commands/change-email.command';
import { UpdateEmailDTO } from './dto/update-email.dto';
import { RegisterDTO } from './dto/register.dto';
import { BUSES_TOKENS } from '@bitloops/bl-boilerplate-infra-nest-jetstream';
import { UpdateEmailRequestDto } from './dto/update-email.dto';
import { RegisterRequestDto } from './dto/register.dto';
import { BUSES_TOKENS } from '@lib/infra/nest-jetstream';
import {
Application,
Infra,
Expand All @@ -22,8 +22,8 @@ import {
AuthService,
JwtAuthGuard,
LocalAuthGuard,
} from '@bitloops/bl-boilerplate-infra-nest-auth-passport';
import { Traceable } from '@bitloops/bl-boilerplate-infra-telemetry';
} from '@lib/infra/nest-auth-passport';
import { Traceable } from '@lib/infra/telemetry';

@Controller('auth')
export class AuthController {
Expand All @@ -50,8 +50,8 @@ export class AuthController {
}

@UseGuards(JwtAuthGuard)
@Post('updateEmail')
async updateEmail(@Request() req, @Body() dto: UpdateEmailDTO) {
@Patch('updateEmail')
async updateEmail(@Request() req, @Body() dto: UpdateEmailRequestDto) {
console.log('req', req.user);
const command = new ChangeEmailCommand({
email: dto.email,
Expand All @@ -63,7 +63,7 @@ export class AuthController {
}

@Post('register')
async register(@Body() body: RegisterDTO) {
async register(@Body() body: RegisterRequestDto) {
const user = { email: body.email, password: body.password };
const result = await this.authService.register(user);
// const command = new RegisterCommand({
Expand Down
4 changes: 3 additions & 1 deletion backend/src/api/dto/add-todo.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IsString, IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class AddTodoDto {
export class AddTodoRequestDto {
@ApiProperty({ description: 'The title of the todo' })
@IsNotEmpty()
@IsString()
title: string;
Expand Down
3 changes: 0 additions & 3 deletions backend/src/api/dto/complete-todo.dto.ts

This file was deleted.

Loading
Loading