├── docs
├── architecture.png
└── architecture_outbox.png
├── ModularMonolith
├── Responses
│ └── CreatedResponse.cs
├── appsettings.Development.json
├── Configs
│ ├── EventBusConfiguration.cs
│ ├── CoreServicesConfiguration.cs
│ ├── ServicesExtensions.cs
│ └── SwaggerConfiguration.cs
├── Dockerfile
├── Properties
│ └── launchSettings.json
├── appsettings.json
├── Program.cs
├── Controllers
│ ├── HistoryController.cs
│ ├── ProductsController.cs
│ └── UsersController.cs
├── ModularMonolith.csproj
├── Startup.cs
└── .editorconfig
├── ModularMonolith.Contracts
├── Events
│ ├── IDomainEvent.cs
│ ├── IIntegrationEvent.cs
│ └── IEventBus.cs
├── ModularMonolith.Contracts.csproj
└── ProductCratedIntegrationEvent.cs
├── Modules
├── Products
│ ├── ModularMonolith.Products.Domain
│ │ ├── Enums
│ │ │ ├── Color.cs
│ │ │ └── CurrencySymbol.cs
│ │ ├── ValueObjects
│ │ │ └── Money.cs
│ │ ├── Exceptions
│ │ │ ├── InvalidPriceException.cs
│ │ │ └── NameRequiredException.cs
│ │ ├── Interfaces
│ │ │ └── IProductRepository.cs
│ │ ├── ModularMonolith.Products.Domain.csproj
│ │ └── Entities
│ │ │ └── Product.cs
│ ├── ModularMonolith.Products.Application
│ │ ├── Commands
│ │ │ └── AddProduct
│ │ │ │ ├── Requests
│ │ │ │ ├── ColorDto.cs
│ │ │ │ └── AddProductRequest.cs
│ │ │ │ ├── AddProductRequestValidator.cs
│ │ │ │ ├── AddProductCommand.cs
│ │ │ │ └── AddProductCommandHandler.cs
│ │ ├── EventBus
│ │ │ └── IProductEventBus.cs
│ │ ├── Queries
│ │ │ └── GetProduct
│ │ │ │ ├── Responses
│ │ │ │ └── GetProductQueryResponse.cs
│ │ │ │ ├── GetProductQuery.cs
│ │ │ │ └── GetProductQueryHandler.cs
│ │ └── ModularMonolith.Products.Application.csproj
│ └── ModularMonolith.Products.Infrastructure
│ │ ├── ProductsModuleEventBus.cs
│ │ ├── EntitiesConfigurations
│ │ └── ProductConfiguration.cs
│ │ ├── ProductsModuleDbContext.cs
│ │ ├── ProductRepository.cs
│ │ ├── Startup
│ │ └── ProductsModuleStartup.cs
│ │ ├── ModularMonolith.Products.Infrastructure.csproj
│ │ └── Migrations
│ │ ├── 20210502114112_ProductModuleInitialMigration.cs
│ │ ├── 20210502114112_ProductModuleInitialMigration.Designer.cs
│ │ ├── 20230809170122_ExtendProductEntity.cs
│ │ ├── ProductDbContextModelSnapshot.cs
│ │ └── 20230809170122_ExtendProductEntity.Designer.cs
├── History
│ ├── ModularMonolith.History.Domain
│ │ ├── Enums
│ │ │ └── EventType.cs
│ │ ├── ModularMonolith.History.Domain.csproj
│ │ ├── Interfaces
│ │ │ └── IHistoryEntityRepository.cs
│ │ └── Entities
│ │ │ └── EntityHistory.cs
│ ├── ModularMonolith.History.Application
│ │ ├── EventBus
│ │ │ └── IHistoryEventBus.cs
│ │ ├── Queries
│ │ │ └── GetHistory
│ │ │ │ ├── GetHistoryQuery.cs
│ │ │ │ ├── Responses
│ │ │ │ └── GetHistoryQueryResponse.cs
│ │ │ │ └── GetHistoryQueryHandler.cs
│ │ ├── ModularMonolith.History.Application.csproj
│ │ └── EventHandlers
│ │ │ └── Products
│ │ │ └── ProductCreatedEventHandler.cs
│ └── ModularMonolith.History.Infrastructure
│ │ ├── HistoryEventBus.cs
│ │ ├── EntitiesConfigurations
│ │ └── EntityHistoryConfiguration.cs
│ │ ├── Persistence
│ │ ├── HistoryDbContext.cs
│ │ └── EntityHistoryRepository.cs
│ │ ├── ModularMonolith.History.Infrastructure.csproj
│ │ ├── Startup
│ │ └── HistoryModuleStartup.cs
│ │ └── Migrations
│ │ ├── 20210502114900_HistoryModuleInitialMigration.cs
│ │ ├── HistoryDbContextModelSnapshot.cs
│ │ └── 20210502114900_HistoryModuleInitialMigration.Designer.cs
├── User
│ ├── ModularMonolith.User.Domain
│ │ └── ModularMonolith.User.Domain.csproj
│ ├── ModularMonolith.User.Application
│ │ ├── Queries
│ │ │ ├── Login
│ │ │ │ ├── Requests
│ │ │ │ │ └── LoginRequest.cs
│ │ │ │ ├── Responses
│ │ │ │ │ └── LoginResponse.cs
│ │ │ │ ├── LoginQuery.cs
│ │ │ │ └── LoginQueryHandler.cs
│ │ │ └── GetUserDetails
│ │ │ │ ├── GetUserDetailsQuery.cs
│ │ │ │ └── GetUserDetailsQueryHandler.cs
│ │ ├── Interfaces
│ │ │ └── IJwtService.cs
│ │ ├── Exceptions
│ │ │ ├── LoginException.cs
│ │ │ └── RegisterException.cs
│ │ ├── Commands
│ │ │ └── Register
│ │ │ │ ├── Requests
│ │ │ │ └── RegisterRequest.cs
│ │ │ │ ├── RegisterUserCommand.cs
│ │ │ │ └── RegisterUserCommandHandler.cs
│ │ ├── Entities
│ │ │ └── AppUser.cs
│ │ └── ModularMonolith.User.Application.csproj
│ └── ModularMonolith.User.Infrastructure
│ │ ├── UsersDbContext.cs
│ │ ├── Migrations
│ │ ├── 20211117203055_ExtendUser.cs
│ │ ├── 20211022102431_InitAuth.cs
│ │ ├── 20211022102431_InitAuth.Designer.cs
│ │ ├── UserDbContextModelSnapshot.cs
│ │ └── 20211117203055_ExtendUser.Designer.cs
│ │ ├── Services
│ │ └── JwtService.cs
│ │ ├── ModularMonolith.User.Infrastructure.csproj
│ │ └── Startup
│ │ └── UsersModuleStartup.cs
└── OutBox
│ ├── ModularMonolith.Outbox
│ ├── ModularMonolith.Core.csproj
│ ├── EntitiesConfigurations
│ │ └── OutBoxMessageEntityConfiguration.cs
│ ├── ModularMonolith.Outbox.csproj
│ ├── OutBoxModuleStartup.cs
│ ├── Migrations
│ │ ├── 20210527113057_InitOutboxModule.cs
│ │ ├── OutboxDbContextModelSnapshot.cs
│ │ └── 20210527113057_InitOutboxModule.Designer.cs
│ ├── Persistence
│ │ └── OutboxDbContext.cs
│ ├── Entities
│ │ └── OutBoxMessage.cs
│ └── InMemoryEventBus.cs
│ └── ModularMonolith.Outbox.WorkerProcess
│ ├── appsettings.json
│ ├── appsettings.Development.json
│ ├── Properties
│ └── launchSettings.json
│ ├── Program.cs
│ ├── ModularMonolith.Outbox.WorkerProcess.csproj
│ ├── Dockerfile
│ └── OutBoxWorker.cs
├── ModularMonolith.Infrastructure
├── Services
│ ├── IUserContext.cs
│ └── UserContext.cs
├── Behaviours
│ └── RequestValidationBehaviour.cs
├── Exceptions
│ ├── ErrorResponse.cs
│ └── ExceptionLoggingMiddleware.cs
├── ModularMonolith.Infrastructure.csproj
├── Persistence
│ └── BaseDbContext.cs
└── Logging
│ └── LoggerConfiguration.cs
├── ModularMonolith.Exceptions.Abstraction
├── ModularMonolith.Exceptions.Abstraction.csproj
├── DomainException.cs
└── AppException.cs
├── ModularMonolith.User.Contracts
├── IUserService.cs
├── IUserApi.cs
├── ModularMonolith.User.Contracts.csproj
└── UserDto.cs
├── ModularMonolith.Domain
├── ModularMonolith.Domain.csproj
└── Entities
│ └── BaseEntity.cs
├── .dockerignore
├── README.md
├── azure-pipelines.yml
├── .gitattributes
├── .gitignore
└── ModularMonolith.sln
/docs/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ridikk12/ModularMonolith/HEAD/docs/architecture.png
--------------------------------------------------------------------------------
/docs/architecture_outbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ridikk12/ModularMonolith/HEAD/docs/architecture_outbox.png
--------------------------------------------------------------------------------
/ModularMonolith/Responses/CreatedResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ModularMonolith.Responses;
4 |
5 | public record CreatedResponse(Guid Id);
--------------------------------------------------------------------------------
/ModularMonolith.Contracts/Events/IDomainEvent.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Contracts.Events
2 | {
3 | public interface IDomainEvent
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ModularMonolith.Contracts/Events/IIntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Contracts.Events
2 | {
3 | public interface IIntegrationEvent
4 | {
5 |
6 | }
7 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Enums/Color.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Products.Domain.Enums;
2 |
3 | public enum Color
4 | {
5 | Red,
6 | Green,
7 | Blue,
8 | Yellow
9 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Enums/CurrencySymbol.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Products.Domain.Enums;
2 |
3 | public enum CurrencySymbol
4 | {
5 | Usd,
6 | Eur,
7 | Pln
8 | }
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Services/IUserContext.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Infrastructure.Services
2 | {
3 | public interface IUserContext
4 | {
5 | string UserId { get; }
6 | }
7 | }
--------------------------------------------------------------------------------
/ModularMonolith/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logger": {
3 | "EnableConsole": "true",
4 | "LogLevel": "Information",
5 | "EnableFile": "true",
6 | "FilePath": "C:/temp/log.txt"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Domain/Enums/EventType.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.History.Domain.Enums
2 | {
3 | public enum EventType
4 | {
5 | Create,
6 | Update,
7 | Delete
8 | }
9 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Domain/ModularMonolith.User.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/ModularMonolith.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ModularMonolith.Exceptions.Abstraction/ModularMonolith.Exceptions.Abstraction.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Domain/ModularMonolith.History.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/ValueObjects/Money.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Products.Domain.Enums;
2 |
3 | namespace ModularMonolith.Products.Domain.ValueObjects
4 | {
5 | public record Money(decimal Price, CurrencySymbol CurrencySymbol);
6 | }
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Commands/AddProduct/Requests/ColorDto.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Products.Application.Commands.AddProduct.Requests;
2 |
3 | public enum ColorDto
4 | {
5 | Red,
6 | Green,
7 | Blue,
8 | Yellow
9 | }
--------------------------------------------------------------------------------
/ModularMonolith.User.Contracts/IUserService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ModularMonolith.User.Contracts
5 | {
6 | public interface IUserService
7 | {
8 | Task GetUserDetails(string userId);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Commands/AddProduct/Requests/AddProductRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Products.Application.Commands.AddProduct.Requests;
2 |
3 | public record AddProductRequest(string Name, string Description, decimal Price, ColorDto Color);
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/EventBus/IProductEventBus.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Contracts.Events;
2 |
3 | namespace ModularMonolith.Products.Application.EventBus
4 | {
5 | public interface IProductEventBus : IEventBus
6 | {
7 |
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/Login/Requests/LoginRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.User.Application.Queries.Login.Requests
2 | {
3 | public class LoginRequest
4 | {
5 | public string UserName { get; set; }
6 | public string Password { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/ModularMonolith.Domain/ModularMonolith.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ModularMonolith.User.Contracts/IUserApi.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using System.Threading.Tasks;
3 |
4 | namespace ModularMonolith.User.Contracts
5 | {
6 | public interface IUserApi
7 | {
8 | [Get("/user/{userId}")]
9 | public Task GetUserDetails(string userId);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Interfaces/IJwtService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Security.Claims;
3 |
4 | namespace ModularMonolith.User.Application.Interfaces
5 | {
6 | public interface IJwtService
7 | {
8 | string GenerateJwt(List claims);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ModularMonolith.User.Contracts/ModularMonolith.User.Contracts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/EventBus/IHistoryEventBus.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using ModularMonolith.Contracts.Events;
5 |
6 | namespace ModularMonolith.History.Application.EventBus
7 | {
8 | public interface IHistoryEventBus : IEventBus
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ModularMonolith.Outbox": {
4 | "commandName": "Project",
5 | "environmentVariables": {
6 | "DOTNET_ENVIRONMENT": "Development"
7 | }
8 | },
9 | "Docker": {
10 | "commandName": "Docker"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/ModularMonolith.Contracts/Events/IEventBus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | namespace ModularMonolith.Contracts.Events
5 | {
6 | public interface IEventBus
7 | {
8 | public Task Publish(IIntegrationEvent @event);
9 | Task PublishMany(IEnumerable @events);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Exceptions/InvalidPriceException.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Exceptions.Abstraction;
2 |
3 | namespace ModularMonolith.Products.Domain.Exceptions;
4 |
5 | public class InvalidPriceException : DomainException
6 | {
7 | public InvalidPriceException() : base("Price has to be more then 0", 103)
8 | {
9 | }
10 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/Login/Responses/LoginResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.User.Application.Queries.Login.Responses
2 | {
3 | public class LoginResponse
4 | {
5 | public LoginResponse(string token)
6 | {
7 | Token = token;
8 | }
9 | public string Token { get; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ModularMonolith.Contracts/ModularMonolith.Contracts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ModularMonolith.Domain/Entities/BaseEntity.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.Domain.Entities;
2 |
3 | public class BaseEntity
4 | {
5 | public Guid Id { get; set; }
6 | public DateTime CreatedDate { get; set; }
7 | public string CreatedBy { get; set; } = string.Empty;
8 | public DateTime ModifiedDate { get; set; }
9 | public string ModifiedBy { get; set; } = string.Empty;
10 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Exceptions/LoginException.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Exceptions.Abstraction;
2 |
3 | namespace ModularMonolith.User.Application.Exceptions
4 | {
5 | public class LoginException : AppException
6 | {
7 | public LoginException() : base("Unable to login. Wrong Username or Password", 102)
8 | {
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Exceptions/NameRequiredException.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Exceptions.Abstraction;
2 |
3 | namespace ModularMonolith.Products.Domain.Exceptions
4 | {
5 | public class NameRequiredException : DomainException
6 | {
7 | public NameRequiredException() : base("Product name is required.", 100)
8 | {
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ModularMonolith.Exceptions.Abstraction/DomainException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ModularMonolith.Exceptions.Abstraction
4 | {
5 | public abstract class DomainException : Exception
6 | {
7 | public int ExceptionCode { get; }
8 | public DomainException(string message, int exceptionCode) : base(message)
9 | {
10 | ExceptionCode = exceptionCode;
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Commands/Register/Requests/RegisterRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.User.Application.Commands.Register.Requests
2 | {
3 | public class RegisterRequest
4 | {
5 | public string UserName { get; set; }
6 | public string Password { get; set; }
7 | public string Name { get; set; }
8 | public string Surname { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.classpath
2 | **/.dockerignore
3 | **/.env
4 | **/.git
5 | **/.gitignore
6 | **/.project
7 | **/.settings
8 | **/.toolstarget
9 | **/.vs
10 | **/.vscode
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/HistoryEventBus.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.History.Application.EventBus;
2 | using ModularMonolith.Outbox;
3 | using ModularMonolith.Outbox.Persistence;
4 |
5 | namespace ModularMonolith.History.Infrastructure
6 | {
7 | public class HistoryEventBus : InMemoryEventBus, IHistoryEventBus
8 | {
9 | public HistoryEventBus(OutboxDbContext dbContext) : base(dbContext)
10 | {
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Interfaces/IProductRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ModularMonolith.Products.Domain.Entities;
5 |
6 | namespace ModularMonolith.Products.Domain.Interfaces
7 | {
8 | public interface IProductRepository
9 | {
10 | Task Add(Product product);
11 | Task Get(Guid id, CancellationToken cancellationToken);
12 | Task CommitAsync();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Queries/GetProduct/Responses/GetProductQueryResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ModularMonolith.Products.Application.Queries.GetProduct.Responses
4 | {
5 | public class GetProductQueryResponse
6 | {
7 | public GetProductQueryResponse(Guid id, string name)
8 | {
9 | Id = id;
10 | Name = name;
11 | }
12 | public Guid Id { get; }
13 | public string Name { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Queries/GetProduct/GetProductQuery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MediatR;
3 | using ModularMonolith.Products.Application.Queries.GetProduct.Responses;
4 |
5 | namespace ModularMonolith.Products.Application.Queries.GetProduct
6 | {
7 | public class GetProductQuery : IRequest
8 | {
9 | public GetProductQuery(Guid id)
10 | {
11 | Id = id;
12 | }
13 | public Guid Id { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/ProductsModuleEventBus.cs:
--------------------------------------------------------------------------------
1 | using ModularMonolith.Infrastructure;
2 | using ModularMonolith.Outbox;
3 | using ModularMonolith.Products.Application.EventBus;
4 |
5 | namespace ModularMonolith.Products.Infrastructure
6 | {
7 | public class ProductsModuleEventBus : InMemoryEventBus, IProductEventBus
8 | {
9 | public ProductsModuleEventBus(ProductsModuleDbContext dbContext) : base(dbContext)
10 | {
11 |
12 | }
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Entities/AppUser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 |
3 | namespace ModularMonolith.User.Application.Entities
4 | {
5 | public class AppUser : IdentityUser
6 | {
7 | public AppUser(string userName, string name, string surname) : base(userName)
8 | {
9 | Name = name;
10 | Surname = surname;
11 | }
12 | public string Name { get; set; }
13 | public string Surname { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ModularMonolith.User.Contracts/UserDto.cs:
--------------------------------------------------------------------------------
1 | namespace ModularMonolith.User.Contracts
2 | {
3 | public class UserDto
4 | {
5 | public UserDto(string name, string surname, string email)
6 | {
7 | Name = name;
8 | Surname = surname;
9 | Email = email;
10 | }
11 | public string Name { get; }
12 | public string Surname { get; }
13 | public string Email { get; }
14 |
15 | public string FullName => $"{Name} {Surname}";
16 | }
17 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/UsersDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore;
4 | using ModularMonolith.User.Application.Entities;
5 |
6 | namespace ModularMonolith.User.Infrastructure
7 | {
8 | public class UsersDbContext : IdentityDbContext
9 | {
10 | public UsersDbContext(DbContextOptions options) : base(options)
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/GetUserDetails/GetUserDetailsQuery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using MediatR;
5 | using ModularMonolith.User.Contracts;
6 |
7 | namespace ModularMonolith.User.Application.Queries.GetUserDetails
8 | {
9 | public class GetUserDetailsQuery : IRequest
10 | {
11 | public GetUserDetailsQuery(string userId)
12 | {
13 | UserId = userId;
14 | }
15 | public string UserId { get; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Domain/Interfaces/IHistoryEntityRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using ModularMonolith.History.Domain.Entities;
6 |
7 | namespace ModularMonolith.History.Domain.Interfaces
8 | {
9 | public interface IHistoryEntityRepository
10 | {
11 | Task> Get(Guid productId, CancellationToken cancellationToken);
12 | Task Add(EntityHistory history);
13 | Task CommitAsync();
14 | }
15 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/Login/LoginQuery.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using ModularMonolith.User.Application.Queries.Login.Responses;
3 |
4 | namespace ModularMonolith.User.Application.Queries.Login
5 | {
6 | public class LoginQuery : IRequest
7 | {
8 | public LoginQuery(string userName, string password)
9 | {
10 | UserName = userName;
11 | Password = password;
12 | }
13 | public string UserName { get; }
14 | public string Password { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Commands/AddProduct/AddProductRequestValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 |
3 | namespace ModularMonolith.Products.Application.Commands.AddProduct
4 | {
5 | public class AddProductValidator : AbstractValidator
6 | {
7 | public AddProductValidator()
8 | {
9 | RuleFor(x => x.Name).NotEmpty();
10 | RuleFor(x => x.Description).NotEmpty();
11 | RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price has to be more than 0");
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/ModularMonolith/Configs/EventBusConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using ModularMonolith.Contracts.Events;
3 | using ModularMonolith.Infrastructure;
4 | using ModularMonolith.Outbox;
5 |
6 | namespace ModularMonolith.Configs
7 | {
8 | public static class EventBusConfiguration
9 | {
10 | public static IServiceCollection AddInMemoryEventBus(
11 | this IServiceCollection services)
12 | {
13 | services.AddScoped();
14 | return services;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/Queries/GetHistory/GetHistoryQuery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using MediatR;
4 | using ModularMonolith.History.Application.Queries.GetHistory.Responses;
5 |
6 | namespace ModularMonolith.History.Application.Queries.GetHistory
7 | {
8 | public class GetHistoryQuery : IRequest>, IRequest
9 | {
10 | public GetHistoryQuery(Guid entityId)
11 | {
12 | EntityId = entityId;
13 | }
14 | public Guid EntityId { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/EntitiesConfigurations/OutBoxMessageEntityConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using ModularMonolith.Outbox.Entities;
4 |
5 | namespace ModularMonolith.Outbox.EntitiesConfigurations
6 | {
7 | public class OutBoxMessageConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder.ToTable("OutBoxMessages", "out");
12 | builder.HasKey(x => x.Id);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/ModularMonolith.Products.Domain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | ModularMonolith.Products.Domain
6 | ModularMonolith.Products.Domain
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ModularMonolith.Contracts/ProductCratedIntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using ModularMonolith.Contracts.Events;
3 | using System;
4 |
5 | namespace ModularMonolith.Contracts
6 | {
7 | public class ProductCratedIntegrationEvent : INotification, IIntegrationEvent
8 | {
9 | public ProductCratedIntegrationEvent(Guid id, string name, string description)
10 | {
11 | Name = name;
12 | Description = description;
13 | Id = id;
14 | }
15 |
16 | public Guid Id { get; set; }
17 | public string Name { get; set; }
18 | public string Description { get; set; }
19 | }
20 | }
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/Queries/GetHistory/Responses/GetHistoryQueryResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ModularMonolith.History.Application.Queries.GetHistory.Responses
4 | {
5 | public class GetHistoryQueryResponse
6 | {
7 | public GetHistoryQueryResponse(string name, DateTime createDate, string eventType)
8 | {
9 | Name = name;
10 | CreateDate = createDate;
11 | EventType = eventType;
12 | }
13 | public string Name { get; }
14 | public DateTime CreateDate { get; }
15 | public string EventType { get; }
16 |
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Exceptions/RegisterException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Microsoft.AspNetCore.Identity;
6 | using ModularMonolith.Exceptions.Abstraction;
7 |
8 | namespace ModularMonolith.User.Application.Exceptions
9 | {
10 | public class RegisterException : ModularMonolithValidationException
11 | {
12 | public RegisterException(IEnumerable errors) : base("Unable to register account.", 102)
13 | {
14 | ValidationMessages = errors.Select(x => x.Description).ToList();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/ModularMonolith.History.Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Commands/Register/RegisterUserCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 |
3 | namespace ModularMonolith.User.Application.Commands.Register
4 | {
5 | public class RegisterUserCommand : IRequest
6 | {
7 | public RegisterUserCommand(string userName, string password, string name, string surname)
8 | {
9 | UserName = userName;
10 | Password = password;
11 | Name = name;
12 | Surname = surname;
13 | }
14 |
15 | public string UserName { get; }
16 | public string Password { get; }
17 | public string Name { get; }
18 | public string Surname { get; }
19 | }
20 | }
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Services/UserContext.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Microsoft.AspNetCore.Http;
3 |
4 | namespace ModularMonolith.Infrastructure.Services
5 | {
6 | public class UserContext : IUserContext
7 | {
8 | private readonly IHttpContextAccessor _contextAccessor;
9 |
10 | public UserContext(IHttpContextAccessor contextAccessor)
11 | {
12 | _contextAccessor = contextAccessor;
13 | }
14 |
15 | public string UserId
16 | {
17 | get
18 | {
19 | return _contextAccessor?.HttpContext.User.Claims.First(x => x.Type == "UserId").Value;
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | namespace ModularMonolith.Outbox.WorkerProcess
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IHostBuilder CreateHostBuilder(string[] args) =>
14 | Host.CreateDefaultBuilder(args)
15 | .ConfigureServices((hostContext, services) =>
16 | {
17 | services.AddHostedService();
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/ModularMonolith.Outbox.WorkerProcess.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | dotnet-ModularMonolith.Outbox-EECD4DC7-9906-4CAF-BE7C-0DFC83166F04
6 | Linux
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/ModularMonolith.Outbox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Commands/AddProduct/AddProductCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MediatR;
3 | using ModularMonolith.Products.Application.Commands.AddProduct.Requests;
4 |
5 | namespace ModularMonolith.Products.Application.Commands.AddProduct
6 | {
7 | public class AddProductCommand : IRequest
8 | {
9 | public AddProductCommand(string name, string description, decimal price, ColorDto color)
10 | {
11 | Name = name;
12 | Description = description;
13 | Price = price;
14 | Color = color;
15 | }
16 |
17 | public string Name { get; }
18 | public string Description { get; }
19 | public decimal Price { get; }
20 | public ColorDto Color { get; }
21 | }
22 | }
--------------------------------------------------------------------------------
/ModularMonolith/Dockerfile:
--------------------------------------------------------------------------------
1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
2 |
3 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
4 | WORKDIR /app
5 | EXPOSE 80
6 | EXPOSE 443
7 |
8 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
9 | WORKDIR /src
10 | COPY ["ModularMonolith/ModularMonolith.csproj", "ModularMonolith/"]
11 | RUN dotnet restore "ModularMonolith/ModularMonolith.csproj"
12 | COPY . .
13 | WORKDIR "/src/ModularMonolith"
14 | RUN dotnet build "ModularMonolith.csproj" -c Release -o /app/build
15 |
16 | FROM build AS publish
17 | RUN dotnet publish "ModularMonolith.csproj" -c Release -o /app/publish
18 |
19 | FROM base AS final
20 | WORKDIR /app
21 | COPY --from=publish /app/publish .
22 | ENTRYPOINT ["dotnet", "ModularMonolith.dll"]
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/OutBoxModuleStartup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using ModularMonolith.Infrastructure.Persistence;
5 | using ModularMonolith.Outbox.Persistence;
6 |
7 | namespace ModularMonolith.Outbox
8 | {
9 | public static class OutBoxModuleStartup
10 | {
11 | public static IServiceCollection AddOutBoxModule(
12 | this IServiceCollection services, IConfiguration configuration)
13 | {
14 | services.AddDbContext(x =>
15 | {
16 | var connectionString = configuration["Modules:OutBoxModule:DbConnectionString"];
17 | x.UseSqlServer(connectionString);
18 | });
19 |
20 | return services;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/Dockerfile:
--------------------------------------------------------------------------------
1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
2 |
3 | FROM mcr.microsoft.com/dotnet/core/runtime:3.1-buster-slim AS base
4 | WORKDIR /app
5 |
6 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
7 | WORKDIR /src
8 | COPY ["ModularMonolith.Outbox/ModularMonolith.Outbox.csproj", "ModularMonolith.Outbox/"]
9 | RUN dotnet restore "ModularMonolith.Outbox/ModularMonolith.Outbox.csproj"
10 | COPY . .
11 | WORKDIR "/src/ModularMonolith.Outbox"
12 | RUN dotnet build "ModularMonolith.Outbox.csproj" -c Release -o /app/build
13 |
14 | FROM build AS publish
15 | RUN dotnet publish "ModularMonolith.Outbox.csproj" -c Release -o /app/publish
16 |
17 | FROM base AS final
18 | WORKDIR /app
19 | COPY --from=publish /app/publish .
20 | ENTRYPOINT ["dotnet", "ModularMonolith.Outbox.dll"]
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/Migrations/20210527113057_InitOutboxModule.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace ModularMonolith.Outbox.Migrations
4 | {
5 | public partial class InitOutboxModule : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.EnsureSchema(
10 | name: "out");
11 |
12 | migrationBuilder.RenameTable(
13 | name: "OutBoxMessages",
14 | newName: "OutBoxMessages",
15 | newSchema: "out");
16 | }
17 |
18 | protected override void Down(MigrationBuilder migrationBuilder)
19 | {
20 | migrationBuilder.RenameTable(
21 | name: "OutBoxMessages",
22 | schema: "out",
23 | newName: "OutBoxMessages");
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/ModularMonolith.User.Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/EntitiesConfigurations/EntityHistoryConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
6 | using ModularMonolith.History.Domain;
7 | using ModularMonolith.History.Domain.Entities;
8 |
9 | namespace ModularMonolith.History.Infrastructure.EntitiesConfigurations
10 | {
11 | public class EntityHistoryConfiguration : IEntityTypeConfiguration
12 | {
13 | public void Configure(EntityTypeBuilder builder)
14 | {
15 | builder.Property(x => x.EntityName).HasMaxLength(100);
16 | builder.Property(x => x.EventType).HasConversion();
17 | builder.Property(x => x.RaisedBy).HasMaxLength(50);
18 | builder.HasKey(x => x.Id);
19 |
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/EntitiesConfigurations/ProductConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
3 | using ModularMonolith.Products.Domain.Entities;
4 |
5 | namespace ModularMonolith.Products.Infrastructure.EntitiesConfigurations
6 | {
7 | public class ProductConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder.Property(x => x.Description).HasMaxLength(100);
12 | builder.HasKey(x => x.Id);
13 | builder.Property(x => x.CreatedBy).HasMaxLength(100);
14 | builder.Property(x => x.Name).HasMaxLength(100);
15 | builder.OwnsOne(x => x.Price, p => { p.Property(x => x.Price).HasPrecision(4); });
16 | builder.Property(x => x.Color).HasConversion();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/Persistence/OutboxDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using ModularMonolith.Infrastructure.Persistence;
3 | using ModularMonolith.Infrastructure.Services;
4 | using ModularMonolith.Outbox.Entities;
5 |
6 | namespace ModularMonolith.Outbox.Persistence
7 | {
8 | public class OutboxDbContext : BaseDbContext
9 | {
10 | public DbSet OutBoxMessages { get; set; }
11 |
12 | protected OutboxDbContext(DbContextOptions options, IUserContext userContext) : base(options,
13 | userContext)
14 | {
15 | }
16 |
17 | public OutboxDbContext(DbContextOptions options) : base(options)
18 | {
19 | }
20 |
21 | protected override void OnModelCreating(ModelBuilder modelBuilder)
22 | {
23 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(OutBoxMessage).Assembly);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/ModularMonolith/Configs/CoreServicesConfiguration.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using MediatR;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using ModularMonolith.Contracts;
5 | using ModularMonolith.Infrastructure.Behaviours;
6 | using ModularMonolith.Infrastructure.Services;
7 | using ModularMonolith.Products.Application.Commands.AddProduct;
8 |
9 | namespace ModularMonolith.Configs;
10 |
11 | public static class CoreServicesConfiguration
12 | {
13 | public static void AddApplicationCoreServices(this IServiceCollection services)
14 | {
15 | services.AddMediatR(typeof(ProductCratedIntegrationEvent));
16 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
17 | services.AddValidatorsFromAssemblyContaining();
18 |
19 | services.AddInMemoryEventBus();
20 | services.AddHttpContextAccessor();
21 |
22 | services.AddScoped();
23 |
24 | }
25 | }
--------------------------------------------------------------------------------
/ModularMonolith.Exceptions.Abstraction/AppException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace ModularMonolith.Exceptions.Abstraction
5 | {
6 | public abstract class AppException : Exception
7 | {
8 | public int ExceptionCode { get; }
9 |
10 | protected AppException(string message, int exceptionCode) : base(message)
11 | {
12 | ExceptionCode = exceptionCode;
13 | }
14 | }
15 |
16 | public abstract class ModularMonolithValidationException : AppException
17 | {
18 | public List ValidationMessages { get; set; }
19 | protected ModularMonolithValidationException(string message, int exceptionCode) : base(message, exceptionCode)
20 | {
21 | }
22 | }
23 |
24 | public class NotFoundException : AppException
25 | {
26 | public NotFoundException(string entityId, string entityType) : base($"Entity {entityType} {entityId} was not found.", 9000)
27 | {
28 |
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/Entities/OutBoxMessage.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace ModularMonolith.Outbox.Entities
5 | {
6 | public class OutBoxMessage
7 | {
8 | protected OutBoxMessage()
9 | {
10 |
11 | }
12 |
13 | public Guid Id { get; private set; }
14 | public string Message { get; private set; }
15 | public DateTime SavedOn { get; private set; }
16 | public DateTime? ExecutedOn { get; set; }
17 | public string Type { get; private set; }
18 |
19 | public static OutBoxMessage New(object message)
20 | {
21 | var serializedMessage = JsonConvert.SerializeObject(message);
22 | var type = message.GetType().ToString();
23 |
24 | return new OutBoxMessage
25 | {
26 | Message = serializedMessage,
27 | Type = type,
28 | SavedOn = DateTime.UtcNow,
29 | Id = Guid.NewGuid()
30 | };
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/ProductsModuleDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using ModularMonolith.Infrastructure.Services;
3 | using ModularMonolith.Outbox.Persistence;
4 | using ModularMonolith.Products.Domain.Entities;
5 | using ModularMonolith.Products.Infrastructure.EntitiesConfigurations;
6 |
7 | namespace ModularMonolith.Products.Infrastructure
8 | {
9 | public class ProductsModuleDbContext : OutboxDbContext
10 | {
11 | protected override void OnModelCreating(ModelBuilder modelBuilder)
12 | {
13 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProductConfiguration).Assembly);
14 | modelBuilder.HasDefaultSchema("pr");
15 | base.OnModelCreating(modelBuilder);
16 | }
17 |
18 | public DbSet Products { get; set; }
19 |
20 | public ProductsModuleDbContext(DbContextOptions options, IUserContext userContext) : base(options, userContext)
21 | {
22 |
23 | }
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ModularMonolith/Configs/ServicesExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using ModularMonolith.History.Infrastructure.Startup;
4 | using ModularMonolith.Outbox;
5 | using ModularMonolith.Outbox.WorkerProcess;
6 | using ModularMonolith.Products.Infrastructure.Startup;
7 | using ModularMonolith.User.Infrastructure.Startup;
8 |
9 | namespace ModularMonolith.Configs;
10 |
11 | public static class ServicesExtensions
12 | {
13 | public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
14 | {
15 | services.AddControllers();
16 | services.AddRouting(x => x.LowercaseUrls = true);
17 |
18 | services.AddProductModule(configuration)
19 | .AddHistoryModule(configuration)
20 | .AddOutBoxModule(configuration)
21 | .AddUserModule(configuration);
22 |
23 | services.AddApplicationCoreServices();
24 | services.AddApplicationSwagger();
25 |
26 | services.AddHostedService();
27 | }
28 | }
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Domain/Entities/EntityHistory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ModularMonolith.History.Domain.Enums;
3 |
4 | namespace ModularMonolith.History.Domain.Entities
5 | {
6 | public class EntityHistory
7 | {
8 | private EntityHistory()
9 | {
10 | }
11 |
12 | private EntityHistory(string entityName, Guid entityId, EventType eventType)
13 | {
14 | EntityName = entityName;
15 | EntityId = entityId;
16 | EventType = eventType;
17 | }
18 |
19 | public Guid Id { get; private set; }
20 | public string EntityName { get; private set; }
21 | public Guid EntityId { get; private set; }
22 | public EventType EventType { get; private set; }
23 | public string RaisedBy { get; private set; }
24 | public DateTime RaisedOn { get; private set; }
25 |
26 | public static EntityHistory Create(Guid entityId, string entityName, EventType eventType) =>
27 | new EntityHistory(entityName, entityId, eventType);
28 | }
29 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ModularMonolith
2 | [](https://dev.azure.com/ndebosz/ModularMonolith/_build/latest?definitionId=1&branchName=master)
3 |
4 | [If you feel my articles/code help you - buy me a coffee ☕](https://www.buymeacoffee.com/Ridikk12a)
5 |
6 | The step by step approach to modular-monolith architecture style.
7 | # Part1 MVP:
8 | 
9 |
10 |
11 | # Part2 OutBox:
12 | 
13 |
14 |
15 | # Articles:
16 | Part1 - https://ridikk12.medium.com/easy-modular-monolith-part-1-mvp-d57f47935e24
17 |
18 | Part2 - https://ridikk12.medium.com/easy-modular-monolith-part-2-the-outbox-pattern-b4566724fb68
19 |
20 | Part3 - https://ridikk12.medium.com/easy-modular-monolith-part-3-logging-57caceac1ff5
21 |
22 | Part4 - https://ridikk12.medium.com/easy-modular-monolith-part-4-global-exception-handling-8355cc4905d4
23 |
24 | Part5 - https://itnext.io/easy-modular-monolith-part-5-jwt-authentication-authorization-f7a0a275226f
25 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Persistence/HistoryDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using ModularMonolith.History.Domain.Entities;
3 | using ModularMonolith.History.Infrastructure.EntitiesConfigurations;
4 | using ModularMonolith.Infrastructure.Persistence;
5 | using ModularMonolith.Infrastructure.Services;
6 | using ModularMonolith.Outbox.Persistence;
7 |
8 | namespace ModularMonolith.History.Infrastructure.Persistence
9 | {
10 | public class HistoryDbContext : OutboxDbContext
11 | {
12 | protected override void OnModelCreating(ModelBuilder modelBuilder)
13 | {
14 | modelBuilder.HasDefaultSchema("hi");
15 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(EntityHistoryConfiguration).Assembly);
16 | base.OnModelCreating(modelBuilder);
17 | }
18 |
19 | public DbSet EntityHistories { get; set; }
20 |
21 | public HistoryDbContext(DbContextOptions options, IUserContext userContext) : base(options,
22 | userContext)
23 | {
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/ProductRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Microsoft.EntityFrameworkCore;
5 | using ModularMonolith.Products.Domain.Entities;
6 | using ModularMonolith.Products.Domain.Interfaces;
7 |
8 | namespace ModularMonolith.Products.Infrastructure
9 | {
10 | public class ProductRepository : IProductRepository
11 | {
12 | private readonly ProductsModuleDbContext _dbContext;
13 | public ProductRepository(ProductsModuleDbContext dbContext)
14 | {
15 | _dbContext = dbContext;
16 | }
17 |
18 | public async Task Add(Domain.Entities.Product product)
19 | {
20 | await _dbContext.AddAsync(product);
21 | }
22 |
23 | public Task Get(Guid id, CancellationToken cancellationToken)
24 | {
25 | return _dbContext.Products.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
26 | }
27 |
28 | public Task CommitAsync()
29 | {
30 | return _dbContext.SaveChangesAsync();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/EventHandlers/Products/ProductCreatedEventHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using MediatR;
4 | using ModularMonolith.Contracts;
5 | using ModularMonolith.History.Domain.Entities;
6 | using ModularMonolith.History.Domain.Enums;
7 | using ModularMonolith.History.Domain.Interfaces;
8 |
9 | namespace ModularMonolith.History.Application.EventHandlers.Products
10 | {
11 | public class ProductCreatedEventHandler : INotificationHandler
12 | {
13 | private readonly IHistoryEntityRepository _repository;
14 |
15 | public ProductCreatedEventHandler(IHistoryEntityRepository repository)
16 | {
17 | _repository = repository;
18 | }
19 | public async Task Handle(ProductCratedIntegrationEvent notification, CancellationToken cancellationToken)
20 | {
21 | var history = EntityHistory.Create(notification.Id, notification.Name, EventType.Create);
22 | await _repository.Add(history);
23 | await _repository.CommitAsync();
24 | }
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ModularMonolith/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:57970",
7 | "sslPort": 44311
8 | }
9 | },
10 | "$schema": "http://json.schemastore.org/launchsettings.json",
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "swagger",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "ModularMonolith": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "swagger",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | },
27 | "applicationUrl": "https://localhost:5001;http://localhost:5000"
28 | },
29 | "Docker": {
30 | "commandName": "Docker",
31 | "launchBrowser": true,
32 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
33 | "publishAllPorts": true,
34 | "useSSL": true
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Commands/Register/RegisterUserCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.AspNetCore.Identity;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using ModularMonolith.User.Application.Entities;
6 | using ModularMonolith.User.Application.Exceptions;
7 |
8 | namespace ModularMonolith.User.Application.Commands.Register
9 | {
10 | public class RegisterUserCommandHandler : IRequestHandler
11 | {
12 | private readonly UserManager _userManager;
13 |
14 | public RegisterUserCommandHandler(UserManager userManager)
15 | {
16 | _userManager = userManager;
17 | }
18 |
19 | public async Task Handle(RegisterUserCommand request, CancellationToken cancellationToken)
20 | {
21 | var identity = await _userManager.CreateAsync(new AppUser(request.UserName, request.Name, request.Surname),
22 | request.Password);
23 | if (!identity.Succeeded)
24 | throw new RegisterException(identity.Errors);
25 |
26 | return Unit.Value;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/Migrations/20211117203055_ExtendUser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace ModularMonolith.User.Infrastructure.Migrations
4 | {
5 | public partial class ExtendUser : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.AddColumn(
10 | name: "Name",
11 | table: "AspNetUsers",
12 | type: "nvarchar(max)",
13 | nullable: true);
14 |
15 | migrationBuilder.AddColumn(
16 | name: "Surname",
17 | table: "AspNetUsers",
18 | type: "nvarchar(max)",
19 | nullable: true);
20 | }
21 |
22 | protected override void Down(MigrationBuilder migrationBuilder)
23 | {
24 | migrationBuilder.DropColumn(
25 | name: "Name",
26 | table: "AspNetUsers");
27 |
28 | migrationBuilder.DropColumn(
29 | name: "Surname",
30 | table: "AspNetUsers");
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ModularMonolith/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logger": {
3 | "EnableConsole": "false",
4 | "LogLevel": "Error",
5 | "EnableFile": "true",
6 | "Seq": {
7 | "Enable": "true",
8 | "URL": "http://localhost:5341/",
9 | "ApiKey": "YouApiKey"
10 | },
11 | "FilePath": "C:/temp/log.txt"
12 | },
13 | "Jwt": {
14 | "Secret": "InFutureWeWillStoreThisInOtherPlaceAsHereItIsNotSecure",
15 | "Issuer": "ModularMonolith.com"
16 | },
17 | "Modules": {
18 | "UsersModule": {
19 | "DbConnectionString" : "Server=localhost;Database=ModularMonolith;Integrated Security=True;TrustServerCertificate=True",
20 | "Url": "http://localhost:5000/"
21 | },
22 | "ProductsModule": {
23 | "DbConnectionString" : "Server=localhost;Database=ModularMonolith;Integrated Security=True;TrustServerCertificate=True"
24 | },
25 | "HistoryModule": {
26 | "DbConnectionString" : "Server=localhost;Database=ModularMonolith;Integrated Security=True;TrustServerCertificate=True"
27 | },
28 | "OutBoxModule": {
29 | "DbConnectionString" : "Server=localhost;Database=ModularMonolith;Integrated Security=True;TrustServerCertificate=True"
30 | }
31 | },
32 | "AllowedHosts": "*"
33 | }
34 |
--------------------------------------------------------------------------------
/ModularMonolith/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Hosting;
4 | using Serilog;
5 | using ModularMonolith.Infrastructure.Logging;
6 | using LoggerConfiguration = Serilog.LoggerConfiguration;
7 |
8 | namespace ModularMonolith
9 | {
10 | public class Program
11 | {
12 | public static void Main(string[] args)
13 | {
14 | Log.Logger = new LoggerConfiguration()
15 | .WriteTo.Console()
16 | .CreateLogger();
17 |
18 | try {
19 | Log.Information("Starting web host");
20 | CreateHostBuilder(args).Build().Run();
21 | }
22 | catch (Exception ex) {
23 | Log.Fatal(ex, "Host terminated unexpectedly");
24 | }
25 | finally {
26 | Log.CloseAndFlush();
27 | }
28 | }
29 |
30 | public static IHostBuilder CreateHostBuilder(string[] args) =>
31 | Host.CreateDefaultBuilder(args)
32 | .UseSerilogLogger()
33 | .ConfigureWebHostDefaults(webBuilder => {
34 | webBuilder.UseStartup();
35 | });
36 | }
37 | }
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Application/Queries/GetHistory/GetHistoryQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using MediatR;
6 | using ModularMonolith.History.Application.Queries.GetHistory.Responses;
7 | using ModularMonolith.History.Domain.Interfaces;
8 |
9 | namespace ModularMonolith.History.Application.Queries.GetHistory
10 | {
11 | public class GetHistoryQueryHandler : IRequestHandler>
12 | {
13 | private readonly IHistoryEntityRepository _historyEntityRepository;
14 |
15 | public GetHistoryQueryHandler(IHistoryEntityRepository historyEntityRepository)
16 | {
17 | _historyEntityRepository = historyEntityRepository;
18 | }
19 | public async Task> Handle(GetHistoryQuery request, CancellationToken cancellationToken)
20 | {
21 | var history = await _historyEntityRepository.Get(request.EntityId, cancellationToken);
22 |
23 | return history.Select(x => new GetHistoryQueryResponse(x.EntityName, x.RaisedOn, x.EventType.ToString()))
24 | .ToList();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Startup/ProductsModuleStartup.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using ModularMonolith.Products.Application.Commands.AddProduct;
6 | using ModularMonolith.Products.Application.EventBus;
7 | using ModularMonolith.Products.Domain.Interfaces;
8 |
9 | namespace ModularMonolith.Products.Infrastructure.Startup
10 | {
11 | public static class ProductsModuleStartup
12 | {
13 | public static IServiceCollection AddProductModule(
14 | this IServiceCollection services, IConfiguration configuration)
15 | {
16 | services.AddMediatR(typeof(AddProductCommand));
17 |
18 | services.AddDbContext(x =>
19 | {
20 | var connectionString = configuration["Modules:ProductsModule:DbConnectionString"];
21 | x.UseSqlServer(connectionString);
22 | });
23 | services.AddScoped();
24 | services.AddScoped();
25 |
26 | return services;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Persistence/EntityHistoryRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Microsoft.EntityFrameworkCore;
7 | using ModularMonolith.History.Domain.Entities;
8 | using ModularMonolith.History.Domain.Interfaces;
9 |
10 | namespace ModularMonolith.History.Infrastructure.Persistence
11 | {
12 | public class EntityHistoryRepository : IHistoryEntityRepository
13 | {
14 | private readonly HistoryDbContext _dbContext;
15 |
16 | public EntityHistoryRepository(HistoryDbContext dbContext)
17 | {
18 | _dbContext = dbContext;
19 | }
20 | public Task> Get(Guid productId, CancellationToken cancellationToken)
21 | {
22 | return _dbContext.EntityHistories.Where(x => x.EntityId == productId).ToListAsync(cancellationToken);
23 | }
24 |
25 | public async Task Add(EntityHistory history)
26 | {
27 | await _dbContext.EntityHistories.AddAsync(history);
28 | }
29 |
30 | public Task CommitAsync()
31 | {
32 | return _dbContext.SaveChangesAsync();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Queries/GetProduct/GetProductQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using MediatR;
4 | using ModularMonolith.Exceptions.Abstraction;
5 | using ModularMonolith.Products.Application.Queries.GetProduct.Responses;
6 | using ModularMonolith.Products.Domain.Entities;
7 | using ModularMonolith.Products.Domain.Interfaces;
8 |
9 | namespace ModularMonolith.Products.Application.Queries.GetProduct
10 | {
11 | public class GetProductQueryHandler : IRequestHandler
12 | {
13 | private readonly IProductRepository _productRepository;
14 |
15 | public GetProductQueryHandler(IProductRepository productRepository)
16 | {
17 | _productRepository = productRepository;
18 | }
19 |
20 | public async Task Handle(GetProductQuery request, CancellationToken cancellationToken)
21 | {
22 | var product = await _productRepository.Get(request.Id, cancellationToken);
23 |
24 | if (product is null)
25 | throw new NotFoundException(request.Id.ToString(), nameof(Product));
26 |
27 | return new GetProductQueryResponse(product.Id, product.Name);
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/InMemoryEventBus.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using ModularMonolith.Contracts.Events;
5 | using ModularMonolith.Outbox.Entities;
6 | using ModularMonolith.Outbox.Persistence;
7 |
8 | namespace ModularMonolith.Outbox
9 | {
10 | public class InMemoryEventBus : IEventBus
11 | {
12 | private readonly OutboxDbContext _dbContext;
13 | public InMemoryEventBus(OutboxDbContext dbContext)
14 | {
15 | _dbContext = dbContext;
16 | }
17 | public async Task Publish(IIntegrationEvent @event)
18 | {
19 | await PersistIntegrationEvent(@event);
20 | }
21 |
22 | private async Task PersistIntegrationEvent(IIntegrationEvent @event)
23 | {
24 | var outBoxMessage = OutBoxMessage.New(@event);
25 | await _dbContext.OutBoxMessages.AddAsync(outBoxMessage);
26 | }
27 | public async Task PublishMany(IEnumerable @events)
28 |
29 | {
30 | foreach (var @event in @events)
31 | {
32 | await PersistIntegrationEvent(@event);
33 | }
34 |
35 | await _dbContext.SaveChangesAsync(CancellationToken.None);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/ModularMonolith.History.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Startup/HistoryModuleStartup.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using ModularMonolith.History.Application.EventBus;
6 | using ModularMonolith.History.Application.Queries;
7 | using ModularMonolith.History.Application.Queries.GetHistory;
8 | using ModularMonolith.History.Domain.Interfaces;
9 | using ModularMonolith.History.Infrastructure.Persistence;
10 |
11 | namespace ModularMonolith.History.Infrastructure.Startup
12 | {
13 | public static class HistoryModuleStartup
14 | {
15 | public static IServiceCollection AddHistoryModule(
16 | this IServiceCollection services, IConfiguration configuration)
17 | {
18 | services.AddMediatR(typeof(GetHistoryQuery));
19 | services.AddDbContext(x =>
20 | {
21 | var connectionString = configuration["Modules:HistoryModule:DbConnectionString"];
22 | x.UseSqlServer(connectionString);
23 | });
24 | services.AddScoped();
25 | services.AddScoped();
26 | return services;
27 | }
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Behaviours/RequestValidationBehaviour.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using FluentValidation;
6 | using MediatR;
7 |
8 | namespace ModularMonolith.Infrastructure.Behaviours
9 | {
10 | public class ValidationBehavior : IPipelineBehavior
11 | where TRequest : IRequest
12 | {
13 | private readonly IEnumerable> _validators;
14 |
15 | public ValidationBehavior(IEnumerable> validators)
16 | {
17 | _validators = validators;
18 | }
19 |
20 | public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next)
21 | {
22 | var context = new ValidationContext(request);
23 |
24 | var failures = _validators
25 | .Select(v => v.Validate(context))
26 | .SelectMany(result => result.Errors)
27 | .Where(error => error != null)
28 | .ToList();
29 |
30 | if (failures.Count != 0)
31 | {
32 | throw new ValidationException(failures);
33 | }
34 |
35 | return await next();
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/GetUserDetails/GetUserDetailsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using Microsoft.AspNetCore.Identity;
3 | using ModularMonolith.Exceptions.Abstraction;
4 | using ModularMonolith.User.Application.Entities;
5 | using ModularMonolith.User.Contracts;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace ModularMonolith.User.Application.Queries.GetUserDetails
10 | {
11 | public class GetUserDetailsQueryHandler : IRequestHandler, IUserService
12 | {
13 | private readonly UserManager _userManager;
14 | public GetUserDetailsQueryHandler(UserManager userManager)
15 | {
16 | _userManager = userManager;
17 | }
18 | public async Task Handle(GetUserDetailsQuery request, CancellationToken cancellationToken)
19 | {
20 | var user = await _userManager.FindByIdAsync(request.UserId);
21 | if (user is null)
22 | throw new NotFoundException(request.UserId, nameof(AppUser));
23 |
24 | return new UserDto(user.UserName, user.Name, user.Surname);
25 | }
26 |
27 | public Task GetUserDetails(string userId)
28 | {
29 | return Handle(new GetUserDetailsQuery(userId), CancellationToken.None);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/Services/JwtService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IdentityModel.Tokens.Jwt;
4 | using System.Security.Claims;
5 | using System.Text;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.IdentityModel.Tokens;
8 | using ModularMonolith.User.Application.Interfaces;
9 |
10 | namespace ModularMonolith.User.Infrastructure.Services
11 | {
12 | public class JwtService : IJwtService
13 | {
14 | private readonly string _signingKey;
15 | private readonly string _issuer;
16 |
17 | public JwtService(IConfiguration configuration)
18 | {
19 | _signingKey = configuration["Jwt:Secret"];
20 | _issuer = configuration["Jwt:Issuer"];
21 | }
22 | public string GenerateJwt(List claims)
23 | {
24 | var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey));
25 | var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
26 |
27 | var token = new JwtSecurityToken(_issuer,
28 | _issuer,
29 | claims,
30 | expires: DateTime.Now.AddMinutes(120),
31 | signingCredentials: credentials);
32 |
33 | return new JwtSecurityTokenHandler().WriteToken(token);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Domain/Entities/Product.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ModularMonolith.Domain.Entities;
3 | using ModularMonolith.Products.Domain.Enums;
4 | using ModularMonolith.Products.Domain.Exceptions;
5 | using ModularMonolith.Products.Domain.ValueObjects;
6 |
7 | namespace ModularMonolith.Products.Domain.Entities
8 | {
9 | public class Product : BaseEntity
10 | {
11 | public string Name { get; }
12 | public string Description { get; }
13 | public Money Price { get; set; }
14 | public Color Color { get; set; }
15 |
16 |
17 | private Product()
18 | {
19 | }
20 |
21 | private Product(string name, string description, Money price, Color color)
22 | {
23 | Name = name;
24 | Description = description;
25 | Price = price;
26 | Color = color;
27 | }
28 |
29 | public static Product New(string name, string description, Money price, Color color)
30 | {
31 | if (string.IsNullOrEmpty(name))
32 | {
33 | throw new NameRequiredException();
34 | }
35 |
36 | if (price.Price < 0)
37 | {
38 | throw new InvalidPriceException();
39 | }
40 |
41 |
42 | return new Product(name, description, price, color);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/ModularMonolith.Products.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | 8cc2beb0-d18c-49b2-9102-c795770155df
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Exceptions/ErrorResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using FluentValidation.Results;
4 |
5 | namespace ModularMonolith.Infrastructure.Exceptions
6 | {
7 | public class ErrorResponse
8 | {
9 | public ErrorResponse(int statusCode, string message)
10 | {
11 | StatusCode = statusCode;
12 | Message = message;
13 | }
14 |
15 | public ErrorResponse(IEnumerable failures, string errorMessage, int statusCode)
16 | {
17 | Errors = failures
18 | .GroupBy(e => e.PropertyName, e => e.ErrorMessage)
19 | .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
20 | Message = errorMessage;
21 | StatusCode = statusCode;
22 | }
23 |
24 | public ErrorResponse(int statusCode, string message, List validationMessages)
25 | {
26 | StatusCode = statusCode;
27 | Message = message;
28 |
29 | Errors = validationMessages
30 | .Select((s, index) => new { s, index })
31 | .ToDictionary(x => (x.index + 1).ToString(), x => new string[] { x.s });
32 | }
33 |
34 | public int StatusCode { get; }
35 | public string Message { get; }
36 |
37 | public IDictionary Errors { get; }
38 | }
39 | }
--------------------------------------------------------------------------------
/ModularMonolith/Configs/SwaggerConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.OpenApi.Models;
6 |
7 | namespace ModularMonolith.Configs;
8 |
9 | public static class SwaggerConfiguration
10 | {
11 | public static void AddApplicationSwagger(this IServiceCollection services) =>
12 | services.AddSwaggerGen(c => {
13 | c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() {
14 | Name = "Bearer",
15 | BearerFormat = "JWT",
16 | Scheme = "bearer",
17 | Description = "Specify the authorization token.",
18 | In = ParameterLocation.Header,
19 | Type = SecuritySchemeType.Http,
20 | });
21 |
22 | c.AddSecurityRequirement(new OpenApiSecurityRequirement {
23 | {
24 | new OpenApiSecurityScheme {
25 | Reference = new OpenApiReference {
26 | Type = ReferenceType.SecurityScheme,
27 | Id = "Bearer"
28 | }
29 | },
30 | Array.Empty()
31 | }
32 | });
33 |
34 | c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
35 |
36 | });
37 | }
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/ModularMonolith.User.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/ModularMonolith.Products.Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | ModularMonolith.Products.Application
6 | ModularMonolith.Products.Application
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/ModularMonolith.Infrastructure.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ModularMonolith/Controllers/HistoryController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using MediatR;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.AspNetCore.Mvc;
9 | using ModularMonolith.History.Application.Queries;
10 | using ModularMonolith.History.Application.Queries.GetHistory;
11 | using ModularMonolith.History.Application.Queries.GetHistory.Responses;
12 | using ModularMonolith.Infrastructure.Exceptions;
13 |
14 | namespace ModularMonolith.Controllers
15 | {
16 | [ApiController]
17 | [Authorize]
18 | [Route("[controller]")]
19 | public class HistoryController : ControllerBase
20 | {
21 | private readonly IMediator _mediator;
22 | public HistoryController(IMediator mediator)
23 | {
24 | _mediator = mediator;
25 | }
26 |
27 | ///
28 | /// Get history of the entity
29 | ///
30 | ///
31 | ///
32 | ///
33 | [ProducesResponseType(typeof(List),StatusCodes.Status200OK)]
34 | [ProducesResponseType(typeof(ErrorResponse),StatusCodes.Status400BadRequest)]
35 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
36 | [HttpGet("{entityId}")]
37 | public async Task History(Guid entityId, CancellationToken cancellationToken) =>
38 | Ok(await _mediator.Send(new GetHistoryQuery(entityId), cancellationToken));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Migrations/20210502114900_HistoryModuleInitialMigration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace ModularMonolith.History.Infrastructure.Migrations
5 | {
6 | public partial class HistoryModuleInitialMigration : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.EnsureSchema(
11 | name: "hi");
12 |
13 | migrationBuilder.CreateTable(
14 | name: "EntityHistories",
15 | schema: "hi",
16 | columns: table => new
17 | {
18 | Id = table.Column(type: "uniqueidentifier", nullable: false),
19 | EntityName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true),
20 | EntityId = table.Column(type: "uniqueidentifier", nullable: false),
21 | EventType = table.Column(type: "nvarchar(max)", nullable: false),
22 | RaisedBy = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
23 | RaisedOn = table.Column(type: "datetime2", nullable: false)
24 | },
25 | constraints: table =>
26 | {
27 | table.PrimaryKey("PK_EntityHistories", x => x.Id);
28 | });
29 | }
30 |
31 | protected override void Down(MigrationBuilder migrationBuilder)
32 | {
33 | migrationBuilder.DropTable(
34 | name: "EntityHistories",
35 | schema: "hi");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Application/Queries/Login/LoginQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Security.Claims;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using MediatR;
6 | using Microsoft.AspNetCore.Identity;
7 | using ModularMonolith.User.Application.Entities;
8 | using ModularMonolith.User.Application.Exceptions;
9 | using ModularMonolith.User.Application.Interfaces;
10 | using ModularMonolith.User.Application.Queries.Login.Responses;
11 |
12 | namespace ModularMonolith.User.Application.Queries.Login
13 | {
14 | public class LoginQueryHandler : IRequestHandler
15 | {
16 | private readonly UserManager _userManager;
17 | private readonly IJwtService _jwtService;
18 |
19 | public LoginQueryHandler(UserManager userManager, IJwtService jwtService)
20 | {
21 | _userManager = userManager;
22 | _jwtService = jwtService;
23 | }
24 | public async Task Handle(LoginQuery request, CancellationToken cancellationToken)
25 | {
26 | var user = await _userManager.FindByNameAsync(request.UserName);
27 |
28 | if (user is null || !await _userManager.CheckPasswordAsync(user, request.Password))
29 | {
30 | throw new LoginException();
31 | }
32 |
33 | var claims = new List
34 | {
35 | new Claim(ClaimTypes.Name, user.UserName),
36 | new Claim(ClaimTypes.Role,"User"),
37 | new Claim("UserId",user.Id)
38 | };
39 |
40 | return new LoginResponse(_jwtService.GenerateJwt(claims));
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 |
4 | pool:
5 | vmImage: 'ubuntu-latest'
6 |
7 | variables:
8 | buildConfiguration: 'Release'
9 |
10 | steps:
11 | - task: UseDotNet@2
12 | inputs:
13 | version: 6.x
14 | includePreviewVersions: true
15 | - task: DotNetCoreCLI@2
16 | displayName: Restore Nuget Packages
17 | inputs:
18 | command: 'restore'
19 | projects: "**/**.sln"
20 | workingDirectory: "$(System.DefaultWorkingDirectory)"
21 | - task: DotNetCoreCLI@2
22 | inputs:
23 | command: 'build'
24 | projects: "**/**.sln"
25 | arguments: '--configuration $(buildConfiguration)'
26 | displayName: 'Build'
27 | - task: DotNetCoreCLI@2
28 | displayName: Run Tests
29 | inputs:
30 | command: 'test'
31 | projects: "**/**.sln"
32 | publishTestResults: true
33 | arguments: '--logger trx /p:CollectCoverage=true "/p:CoverletOutputFormat=\"cobertura,opencover\"" /p:Exclude="[*Tests]*"'
34 | - task: PublishCodeCoverageResults@1
35 | displayName: 'Publish Code Coverage'
36 | inputs:
37 | codeCoverageTool: Cobertura
38 | summaryFileLocation: '**/*coverage.cobertura.xml'
39 | # additionalCodeCoverageFiles:
40 | # - task: DotNetCoreCLI@2
41 | # displayName: 'Publish Application'
42 | # inputs:
43 | # command: publish
44 | # publishWebProjects: True
45 | # arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
46 | # zipAfterPublish: True
47 |
48 | # this code takes all the files in $(Build.ArtifactStagingDirectory) and uploads them as an artifact of your build.
49 | - task: PublishPipelineArtifact@1
50 | inputs:
51 | targetPath: '$(Build.ArtifactStagingDirectory)'
52 | artifactName: 'myWebsiteName'
53 |
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Persistence/BaseDbContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Microsoft.EntityFrameworkCore;
4 | using ModularMonolith.Domain.Entities;
5 | using ModularMonolith.Infrastructure.Services;
6 |
7 | namespace ModularMonolith.Infrastructure.Persistence;
8 |
9 | public class BaseDbContext : DbContext
10 | {
11 | private readonly IUserContext _userContext;
12 |
13 | protected BaseDbContext(DbContextOptions options, IUserContext userContext)
14 | : base(options)
15 | {
16 | _userContext = userContext;
17 | }
18 |
19 | protected BaseDbContext(DbContextOptions options)
20 | : base(options)
21 | {
22 |
23 | }
24 |
25 | protected void SetAuditFields()
26 | {
27 | var changedEntities = ChangeTracker.Entries()
28 | .Where(ce => ce.State is EntityState.Added or EntityState.Modified);
29 |
30 | var userId = _userContext.UserId;
31 |
32 | foreach (var auditableEntity in changedEntities)
33 | {
34 | var currentDate = DateTime.UtcNow;
35 |
36 | switch (auditableEntity.State)
37 | {
38 | case EntityState.Added:
39 | auditableEntity.Entity.CreatedDate = currentDate;
40 | auditableEntity.Entity.ModifiedDate = currentDate;
41 | auditableEntity.Entity.CreatedBy = userId;
42 | auditableEntity.Entity.ModifiedBy = userId;
43 | break;
44 | case EntityState.Modified:
45 | auditableEntity.Entity.ModifiedDate = currentDate;
46 | auditableEntity.Entity.ModifiedBy = userId;
47 | break;
48 | }
49 | }
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/Migrations/OutboxDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using ModularMonolith.Outbox.Persistence;
8 |
9 | namespace ModularMonolith.Outbox.Migrations
10 | {
11 | [DbContext(typeof(OutboxDbContext))]
12 | partial class OutboxDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
19 | .HasAnnotation("ProductVersion", "5.0.5")
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("ModularMonolith.Outbox.Entities.OutBoxMessageEntity", b =>
23 | {
24 | b.Property("Id")
25 | .ValueGeneratedOnAdd()
26 | .HasColumnType("uniqueidentifier");
27 |
28 | b.Property("ExecutedOn")
29 | .HasColumnType("datetime2");
30 |
31 | b.Property("Message")
32 | .HasColumnType("nvarchar(max)");
33 |
34 | b.Property("SavedOn")
35 | .HasColumnType("datetime2");
36 |
37 | b.Property("Type")
38 | .HasColumnType("nvarchar(max)");
39 |
40 | b.HasKey("Id");
41 |
42 | b.ToTable("OutBoxMessages");
43 | });
44 | #pragma warning restore 612, 618
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Application/Commands/AddProduct/AddProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using MediatR;
5 | using ModularMonolith.Contracts;
6 | using ModularMonolith.Infrastructure.Services;
7 | using ModularMonolith.Products.Application.Commands.AddProduct.Requests;
8 | using ModularMonolith.Products.Application.EventBus;
9 | using ModularMonolith.Products.Domain.Entities;
10 | using ModularMonolith.Products.Domain.Enums;
11 | using ModularMonolith.Products.Domain.Interfaces;
12 | using ModularMonolith.Products.Domain.ValueObjects;
13 |
14 | namespace ModularMonolith.Products.Application.Commands.AddProduct
15 | {
16 | public class AddProductCommandHandler : IRequestHandler
17 | {
18 | private readonly IProductEventBus _eventBus;
19 | private readonly IProductRepository _productRepository;
20 |
21 | public AddProductCommandHandler(IProductEventBus eventBus,
22 | IProductRepository productRepository)
23 | {
24 | _eventBus = eventBus;
25 | _productRepository = productRepository;
26 | }
27 |
28 | public async Task Handle(AddProductCommand request, CancellationToken cancellationToken)
29 | {
30 | var color = Color(request.Color);
31 | var product = Product.New(request.Name, request.Description,
32 | new Money(request.Price, CurrencySymbol.Usd), color);
33 |
34 | await _productRepository.Add(product);
35 |
36 | await _eventBus.Publish(new ProductCratedIntegrationEvent(product.Id, request.Name, request.Description));
37 |
38 | await _productRepository.CommitAsync();
39 | return product.Id;
40 | }
41 |
42 | private Color Color(ColorDto colorDto)
43 | => Enum.Parse(colorDto.ToString());
44 | }
45 | }
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Logging/LoggerConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Extensions.Hosting;
3 | using Serilog;
4 | using Serilog.Core;
5 | using Serilog.Events;
6 |
7 | namespace ModularMonolith.Infrastructure.Logging
8 | {
9 | public static class LoggerConfiguration
10 | {
11 | public static IHostBuilder UseSerilogLogger(this IHostBuilder hostBuilder) => hostBuilder.UseSerilog(
12 | (context, configuration) =>
13 | {
14 | bool.TryParse(context.Configuration["Logger:EnableConsole"], out var enableConsole);
15 | bool.TryParse(context.Configuration["Logger:EnableFile"], out var enableFile);
16 | bool.TryParse(context.Configuration["Logger:Seq:Enable"], out var enableSeq);
17 |
18 | Enum.TryParse(context.Configuration["Logger:LogLevel"], out var logLevel);
19 |
20 | if (enableFile)
21 | {
22 | var filePath = context.Configuration["Logger:FilePath"];
23 | if (string.IsNullOrEmpty(filePath))
24 | throw new ArgumentException("Logger file path has to be set.");
25 |
26 | configuration.WriteTo.File(filePath, rollingInterval: RollingInterval.Day);
27 | }
28 |
29 | if (enableConsole)
30 | configuration.WriteTo.Console();
31 |
32 | if (enableSeq)
33 | {
34 | var apiKey = context.Configuration["Logger:Seq:ApiKey"];
35 | var seqUrl = context.Configuration["Logger:Seq:URL"];
36 | configuration.WriteTo.Seq(seqUrl, apiKey: apiKey);
37 | }
38 |
39 | configuration.Enrich.FromLogContext();
40 |
41 | var loggingLevelSwitch = new LoggingLevelSwitch { MinimumLevel = logLevel };
42 | configuration.MinimumLevel.ControlledBy(loggingLevelSwitch);
43 | });
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox/Migrations/20210527113057_InitOutboxModule.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using ModularMonolith.Outbox.Persistence;
9 |
10 | namespace ModularMonolith.Outbox.Migrations
11 | {
12 | [DbContext(typeof(OutboxDbContext))]
13 | [Migration("20210527113057_InitOutboxModule")]
14 | partial class InitOutboxModule
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.5")
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("ModularMonolith.Outbox.Entities.OutBoxMessageEntity", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("ExecutedOn")
31 | .HasColumnType("datetime2");
32 |
33 | b.Property("Message")
34 | .HasColumnType("nvarchar(max)");
35 |
36 | b.Property("SavedOn")
37 | .HasColumnType("datetime2");
38 |
39 | b.Property("Type")
40 | .HasColumnType("nvarchar(max)");
41 |
42 | b.HasKey("Id");
43 |
44 | b.ToTable("OutBoxMessages", "out");
45 | });
46 | #pragma warning restore 612, 618
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Migrations/HistoryDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using ModularMonolith.History.Infrastructure;
8 | using ModularMonolith.History.Infrastructure.Persistence;
9 |
10 | namespace ModularMonolith.History.Infrastructure.Migrations
11 | {
12 | [DbContext(typeof(HistoryDbContext))]
13 | partial class HistoryDbContextModelSnapshot : ModelSnapshot
14 | {
15 | protected override void BuildModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .HasDefaultSchema("hi")
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.5")
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("ModularMonolith.History.Domain.EntityHistory", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("uniqueidentifier");
29 |
30 | b.Property("EntityId")
31 | .HasColumnType("uniqueidentifier");
32 |
33 | b.Property("EntityName")
34 | .HasMaxLength(100)
35 | .HasColumnType("nvarchar(100)");
36 |
37 | b.Property("EventType")
38 | .IsRequired()
39 | .HasColumnType("nvarchar(max)");
40 |
41 | b.Property("RaisedBy")
42 | .HasMaxLength(50)
43 | .HasColumnType("nvarchar(50)");
44 |
45 | b.Property("RaisedOn")
46 | .HasColumnType("datetime2");
47 |
48 | b.HasKey("Id");
49 |
50 | b.ToTable("EntityHistories");
51 | });
52 | #pragma warning restore 612, 618
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ModularMonolith/ModularMonolith.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | 265d06cf-4bf2-48f6-9a9e-8e2557ad1dad
6 | Linux
7 |
8 |
9 |
10 | 1701;1702;1591;
11 | bin\Debug\ModularMonolith.xml
12 |
13 |
14 |
15 | bin\Release\ModularMonolith.xml
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Migrations/20210502114112_ProductModuleInitialMigration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace ModularMonolith.Products.Infrastructure.Migrations
5 | {
6 | public partial class ProductModuleInitialMigration : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.EnsureSchema(
11 | name: "pr");
12 |
13 | migrationBuilder.CreateTable(
14 | name: "BaseEntity",
15 | schema: "pr",
16 | columns: table => new
17 | {
18 | Id = table.Column(type: "uniqueidentifier", nullable: false),
19 | CreatedOn = table.Column(type: "datetime2", nullable: false),
20 | CreatedBy = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true)
21 | },
22 | constraints: table =>
23 | {
24 | table.PrimaryKey("PK_BaseEntity", x => x.Id);
25 | });
26 |
27 | migrationBuilder.CreateTable(
28 | name: "Products",
29 | schema: "pr",
30 | columns: table => new
31 | {
32 | Id = table.Column(type: "uniqueidentifier", nullable: false),
33 | Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true),
34 | Description = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true),
35 | CreatedOn = table.Column(type: "datetime2", nullable: false),
36 | CreatedBy = table.Column(type: "nvarchar(max)", nullable: true)
37 | },
38 | constraints: table =>
39 | {
40 | table.PrimaryKey("PK_Products", x => x.Id);
41 | });
42 | }
43 |
44 | protected override void Down(MigrationBuilder migrationBuilder)
45 | {
46 | migrationBuilder.DropTable(
47 | name: "BaseEntity",
48 | schema: "pr");
49 |
50 | migrationBuilder.DropTable(
51 | name: "Products",
52 | schema: "pr");
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Modules/History/ModularMonolith.History.Infrastructure/Migrations/20210502114900_HistoryModuleInitialMigration.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using ModularMonolith.History.Infrastructure;
9 | using ModularMonolith.History.Infrastructure.Persistence;
10 |
11 | namespace ModularMonolith.History.Infrastructure.Migrations
12 | {
13 | [DbContext(typeof(HistoryDbContext))]
14 | [Migration("20210502114900_HistoryModuleInitialMigration")]
15 | partial class HistoryModuleInitialMigration
16 | {
17 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
18 | {
19 | #pragma warning disable 612, 618
20 | modelBuilder
21 | .HasDefaultSchema("hi")
22 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
23 | .HasAnnotation("ProductVersion", "5.0.5")
24 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
25 |
26 | modelBuilder.Entity("ModularMonolith.History.Domain.EntityHistory", b =>
27 | {
28 | b.Property("Id")
29 | .ValueGeneratedOnAdd()
30 | .HasColumnType("uniqueidentifier");
31 |
32 | b.Property("EntityId")
33 | .HasColumnType("uniqueidentifier");
34 |
35 | b.Property("EntityName")
36 | .HasMaxLength(100)
37 | .HasColumnType("nvarchar(100)");
38 |
39 | b.Property("EventType")
40 | .IsRequired()
41 | .HasColumnType("nvarchar(max)");
42 |
43 | b.Property("RaisedBy")
44 | .HasMaxLength(50)
45 | .HasColumnType("nvarchar(50)");
46 |
47 | b.Property("RaisedOn")
48 | .HasColumnType("datetime2");
49 |
50 | b.HasKey("Id");
51 |
52 | b.ToTable("EntityHistories");
53 | });
54 | #pragma warning restore 612, 618
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ModularMonolith/Controllers/ProductsController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using MediatR;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 | using ModularMonolith.Infrastructure.Exceptions;
9 | using ModularMonolith.Products.Application.Commands.AddProduct;
10 | using ModularMonolith.Products.Application.Commands.AddProduct.Requests;
11 | using ModularMonolith.Products.Application.Queries;
12 | using ModularMonolith.Products.Application.Queries.GetProduct;
13 | using ModularMonolith.Products.Application.Queries.GetProduct.Responses;
14 | using ModularMonolith.Responses;
15 |
16 | namespace ModularMonolith.Controllers
17 | {
18 | [ApiController]
19 | [Authorize]
20 | [Route("[controller]")]
21 | public class ProductsController : ControllerBase
22 | {
23 | private readonly IMediator _mediator;
24 |
25 | public ProductsController(IMediator mediator)
26 | {
27 | _mediator = mediator;
28 | }
29 |
30 | ///
31 | /// Create a new product
32 | ///
33 | ///
34 | ///
35 | /// Id of created product
36 | [HttpPost]
37 | [ProducesResponseType(typeof(CreatedResponse), StatusCodes.Status200OK)]
38 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
39 | [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
40 | public async Task Products(AddProductRequest request, CancellationToken cancellationToken)
41 | {
42 | var result = await _mediator.Send(new AddProductCommand(request.Name, request.Description, request.Price, request.Color),
43 | cancellationToken);
44 | return Ok(new CreatedResponse(result));
45 | }
46 |
47 | ///
48 | /// Get product by id
49 | ///
50 | ///
51 | ///
52 | /// Return product by id
53 | [HttpGet("{id:guid}")]
54 | [ProducesResponseType(typeof(GetProductQueryResponse), StatusCodes.Status200OK)]
55 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
56 | [ProducesResponseType(StatusCodes.Status404NotFound)]
57 | public async Task Products(Guid id, CancellationToken cancellationToken) =>
58 | Ok(await _mediator.Send(new GetProductQuery(id), cancellationToken));
59 | }
60 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/ModularMonolith/Startup.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.AspNetCore.Http.Json;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Hosting;
8 | using ModularMonolith.Configs;
9 | using ModularMonolith.History.Infrastructure.Startup;
10 | using ModularMonolith.Infrastructure.Exceptions;
11 | using ModularMonolith.Outbox;
12 | using ModularMonolith.Outbox.WorkerProcess;
13 | using ModularMonolith.Products.Infrastructure.Startup;
14 | using ModularMonolith.User.Infrastructure.Startup;
15 |
16 | namespace ModularMonolith
17 | {
18 | public class Startup
19 | {
20 | public Startup(IConfiguration configuration)
21 | {
22 | Configuration = configuration;
23 | }
24 |
25 | public IConfiguration Configuration { get; }
26 |
27 | // This method gets called by the runtime. Use this method to add services to the container.
28 | public void ConfigureServices(IServiceCollection services)
29 | {
30 | services.AddControllers()
31 | .AddJsonOptions(options => {
32 | options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
33 | });
34 |
35 | services.AddRouting(x => x.LowercaseUrls = true);
36 |
37 | services.AddProductModule(Configuration)
38 | .AddHistoryModule(Configuration)
39 | .AddOutBoxModule(Configuration)
40 | .AddUserModule(Configuration);
41 |
42 | services.AddApplicationCoreServices();
43 | services.AddApplicationSwagger();
44 |
45 | services.AddHostedService();
46 | }
47 |
48 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
49 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
50 | {
51 | if (env.IsDevelopment()) {
52 | app.UseDeveloperExceptionPage();
53 | }
54 |
55 | //app.UseHttpsRedirection();
56 |
57 | app.UseSwagger();
58 |
59 | // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
60 | // specifying the Swagger JSON endpoint.
61 | app.UseSwaggerUI(c => {
62 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Modular Monolith API");
63 | });
64 |
65 | app.UseRouting();
66 |
67 | app.UseAuthentication();
68 | app.UseAuthorization();
69 | app.UseMiddleware();
70 |
71 | app.UseEndpoints(endpoints => {
72 | endpoints.MapControllers();
73 | });
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Migrations/20210502114112_ProductModuleInitialMigration.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using ModularMonolith.Products.Infrastructure;
9 |
10 | namespace ModularMonolith.Products.Infrastructure.Migrations
11 | {
12 | [DbContext(typeof(ProductsModuleDbContext))]
13 | [Migration("20210502114112_ProductModuleInitialMigration")]
14 | partial class ProductModuleInitialMigration
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasDefaultSchema("pr")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("ProductVersion", "5.0.5")
23 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
24 |
25 | modelBuilder.Entity("ModularMonolith.Products.Domain.BaseEntity", b =>
26 | {
27 | b.Property("Id")
28 | .ValueGeneratedOnAdd()
29 | .HasColumnType("uniqueidentifier");
30 |
31 | b.Property("CreatedBy")
32 | .HasMaxLength(100)
33 | .HasColumnType("nvarchar(100)");
34 |
35 | b.Property("CreatedOn")
36 | .HasColumnType("datetime2");
37 |
38 | b.HasKey("Id");
39 |
40 | b.ToTable("BaseEntity");
41 | });
42 |
43 | modelBuilder.Entity("ModularMonolith.Products.Domain.Product", b =>
44 | {
45 | b.Property("Id")
46 | .ValueGeneratedOnAdd()
47 | .HasColumnType("uniqueidentifier");
48 |
49 | b.Property("CreatedBy")
50 | .HasColumnType("nvarchar(max)");
51 |
52 | b.Property("CreatedOn")
53 | .HasColumnType("datetime2");
54 |
55 | b.Property("Description")
56 | .HasMaxLength(100)
57 | .HasColumnType("nvarchar(100)");
58 |
59 | b.Property("Name")
60 | .HasMaxLength(100)
61 | .HasColumnType("nvarchar(100)");
62 |
63 | b.HasKey("Id");
64 |
65 | b.ToTable("Products");
66 | });
67 | #pragma warning restore 612, 618
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ModularMonolith/Controllers/UsersController.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using MediatR;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Mvc;
6 | using ModularMonolith.Infrastructure.Exceptions;
7 | using ModularMonolith.User.Application.Commands.Register;
8 | using ModularMonolith.User.Application.Commands.Register.Requests;
9 | using ModularMonolith.User.Application.Queries.GetUserDetails;
10 | using ModularMonolith.User.Application.Queries.Login;
11 | using ModularMonolith.User.Application.Queries.Login.Requests;
12 | using ModularMonolith.User.Application.Queries.Login.Responses;
13 | using ModularMonolith.User.Contracts;
14 |
15 | namespace ModularMonolith.Controllers
16 | {
17 | [ApiController]
18 | [Route("[controller]")]
19 | public class UsersController : ControllerBase
20 | {
21 | private readonly IMediator _mediator;
22 |
23 | public UsersController(IMediator mediator)
24 | {
25 | _mediator = mediator;
26 | }
27 |
28 | ///
29 | /// Login using username and password
30 | ///
31 | /// Login request parameter
32 | /// JWT Token
33 | [HttpPost]
34 | [ProducesResponseType(typeof(LoginResponse), StatusCodes.Status200OK)]
35 | [ProducesResponseType(StatusCodes.Status400BadRequest)]
36 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
37 | [Route("login")]
38 | public async Task> LogIn(LoginRequest request)
39 | => Ok(await _mediator.Send(new LoginQuery(request.UserName, request.Password)));
40 |
41 | ///
42 | /// Register new user
43 | ///
44 | /// Login request parameter
45 | /// JWT Token
46 | [HttpPost]
47 | [ProducesResponseType(StatusCodes.Status200OK)]
48 | [ProducesResponseType(typeof(ErrorResponse),StatusCodes.Status400BadRequest)]
49 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
50 | [HttpPost]
51 | [Route("register")]
52 | public async Task Register(RegisterRequest request) =>
53 | Ok(await _mediator.Send(new RegisterUserCommand(request.UserName, request.Password, request.Name,
54 | request.Password)));
55 |
56 | ///
57 | /// Return user's details by userId
58 | ///
59 | ///
60 | ///
61 | ///
62 | [ProducesResponseType(typeof(UserDto),StatusCodes.Status200OK)]
63 | [ProducesResponseType(typeof(ErrorResponse),StatusCodes.Status400BadRequest)]
64 | [ProducesResponseType(StatusCodes.Status500InternalServerError)]
65 | [HttpGet]
66 | [Route("{userId}")]
67 | public async Task> GetUserDetails(string userId, CancellationToken cancellationToken) =>
68 | Ok(await _mediator.Send(new GetUserDetailsQuery(userId), cancellationToken));
69 | }
70 | }
--------------------------------------------------------------------------------
/ModularMonolith.Infrastructure/Exceptions/ExceptionLoggingMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using ModularMonolith.Exceptions.Abstraction;
3 | using Serilog;
4 | using Serilog.Events;
5 | using System;
6 | using System.Net;
7 | using System.Text.Json;
8 | using System.Threading.Tasks;
9 | using FluentValidation;
10 |
11 | namespace ModularMonolith.Infrastructure.Exceptions
12 | {
13 | public class ExceptionLoggingMiddleware
14 | {
15 | private readonly RequestDelegate _next;
16 | private const string ContentType = "application/json";
17 |
18 | private static readonly JsonSerializerOptions DefaultWebOptions = new()
19 | {
20 | PropertyNameCaseInsensitive = true,
21 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
22 | };
23 |
24 | public ExceptionLoggingMiddleware(RequestDelegate next)
25 | {
26 | _next = next;
27 | }
28 |
29 | public async Task Invoke(HttpContext httpContext)
30 | {
31 | try
32 | {
33 | await _next(httpContext);
34 | }
35 | catch (Exception ex)
36 | {
37 | var logLevel = GetLoggerLevel(ex);
38 | Log.Logger.Write(logLevel, ex, "Exception has been thrown");
39 |
40 | httpContext.Response.ContentType = ContentType;
41 | httpContext.Response.StatusCode = GetHttpStatusCode(ex);
42 |
43 | var exceptionMessage = FormatErrorMessage(ex);
44 | var message = JsonSerializer.Serialize(exceptionMessage, DefaultWebOptions);
45 |
46 | await httpContext.Response.WriteAsync(message);
47 | }
48 | }
49 |
50 | private static ErrorResponse FormatErrorMessage(Exception ex) =>
51 | ex switch
52 | {
53 | DomainException domainException =>
54 | new ErrorResponse(domainException.ExceptionCode, domainException.Message),
55 | ModularMonolithValidationException validationException => new ErrorResponse(
56 | validationException.ExceptionCode,
57 | validationException.Message,
58 | validationException.ValidationMessages),
59 | ValidationException validationException => new ErrorResponse(validationException.Errors,
60 | validationException.Message, -1),
61 | AppException appException => new ErrorResponse(appException.ExceptionCode,
62 | appException.Message),
63 | _ => new ErrorResponse(-1, ex.Message),
64 | };
65 |
66 | private static int GetHttpStatusCode(Exception ex)
67 | => ex switch
68 | {
69 | DomainException _ => (int)HttpStatusCode.BadRequest,
70 | AppException _ => (int)HttpStatusCode.BadRequest,
71 | _ => (int)HttpStatusCode.InternalServerError
72 | };
73 |
74 |
75 | private static LogEventLevel GetLoggerLevel(Exception ex)
76 | => ex switch
77 | {
78 | DomainException _ => LogEventLevel.Information,
79 | AppException _ => LogEventLevel.Information,
80 | _ => LogEventLevel.Error
81 | };
82 | }
83 | }
--------------------------------------------------------------------------------
/ModularMonolith/.editorconfig:
--------------------------------------------------------------------------------
1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\Norbert\source\repos\ModularMonolith\ModularMonolith\ codebase based on best match to current usage at 21/05/09
2 | # You can modify the rules from these initially generated values to suit your own policies
3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
4 | [*.cs]
5 |
6 |
7 | #Core editorconfig formatting - indentation
8 |
9 | #use soft tabs (spaces) for indentation
10 | indent_style = space
11 |
12 | #Formatting - new line options
13 |
14 | #require braces to be on a new line for methods and types (also known as "Allman" style)
15 | csharp_new_line_before_open_brace = methods, types
16 |
17 | #Formatting - organize using options
18 |
19 | #sort System.* using directives alphabetically, and place them before other usings
20 | dotnet_sort_system_directives_first = true
21 |
22 | #Formatting - spacing options
23 |
24 | #require a space after a keyword in a control flow statement such as a for loop
25 | csharp_space_after_keywords_in_control_flow_statements = true
26 | #remove space between method call name and opening parenthesis
27 | csharp_space_between_method_call_name_and_opening_parenthesis = false
28 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
29 | csharp_space_between_method_call_parameter_list_parentheses = false
30 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
31 | csharp_space_between_method_declaration_parameter_list_parentheses = false
32 |
33 | #Formatting - wrapping options
34 |
35 | #leave code block on single line
36 | csharp_preserve_single_line_blocks = true
37 |
38 | #Style - Code block preferences
39 |
40 | #prefer curly braces even for one line of code
41 | csharp_prefer_braces = true:suggestion
42 |
43 | #Style - expression bodied member options
44 |
45 | #prefer block bodies for constructors
46 | csharp_style_expression_bodied_constructors = false:suggestion
47 | #prefer expression-bodied members for methods when they will be a single line
48 | csharp_style_expression_bodied_methods = when_on_single_line:suggestion
49 |
50 | #Style - language keyword and framework type options
51 |
52 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
53 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
54 |
55 | #Style - modifier options
56 |
57 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
58 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
59 |
60 | #Style - Modifier preferences
61 |
62 | #when this rule is set to a list of modifiers, prefer the specified ordering.
63 | csharp_preferred_modifier_order = public,private,static,async,readonly:suggestion
64 |
65 | #Style - qualification options
66 |
67 | #prefer fields not to be prefaced with this. or Me. in Visual Basic
68 | dotnet_style_qualification_for_field = false:suggestion
69 | #prefer properties not to be prefaced with this. or Me. in Visual Basic
70 | dotnet_style_qualification_for_property = false:suggestion
71 |
--------------------------------------------------------------------------------
/Modules/OutBox/ModularMonolith.Outbox.WorkerProcess/OutBoxWorker.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using System.Text.Json;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using MediatR;
8 | using Microsoft.EntityFrameworkCore;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Hosting;
11 | using Microsoft.Extensions.Logging;
12 | using ModularMonolith.Contracts.Events;
13 | using ModularMonolith.Outbox.Persistence;
14 |
15 | namespace ModularMonolith.Outbox.WorkerProcess
16 | {
17 | public class OutBoxWorker : BackgroundService
18 | {
19 | private readonly ILogger _logger;
20 | private readonly IServiceProvider _serviceProvider;
21 |
22 | public OutBoxWorker(ILogger logger, IServiceProvider serviceProvider)
23 | {
24 | _logger = logger;
25 | _serviceProvider = serviceProvider;
26 | }
27 |
28 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
29 | {
30 | while (!stoppingToken.IsCancellationRequested)
31 | {
32 | using var scope = _serviceProvider.CreateScope();
33 |
34 | try
35 | {
36 | var services = GetServices(scope);
37 | var messages = await services.dbContext.OutBoxMessages.Where(x => x.ExecutedOn == null)
38 | .ToListAsync(stoppingToken);
39 |
40 | foreach (var outBoxMessage in messages)
41 | {
42 | try
43 | {
44 | var eventType = Assembly.Load(typeof(IEventBus).Assembly.GetName())
45 | .GetType(outBoxMessage.Type);
46 |
47 | var @event = JsonSerializer.Deserialize(outBoxMessage.Message, eventType);
48 |
49 | await services.mediator.Publish(@event, CancellationToken.None);
50 |
51 | outBoxMessage.ExecutedOn = DateTime.UtcNow;
52 | await services.dbContext.SaveChangesAsync(CancellationToken.None);
53 |
54 | _logger.LogInformation("[Outbox] Message handled: {MessageId}", outBoxMessage.Id);
55 |
56 | }
57 | catch (Exception ex)
58 | {
59 | _logger.LogError(ex, "[Outbox] Exception when handling message. Message body: {message}",
60 | outBoxMessage.Message);
61 | }
62 | }
63 |
64 | await Task.Delay(1000, stoppingToken);
65 | }
66 | catch (Exception ex)
67 | {
68 | _logger.LogError(ex, "Unhandled exception");
69 | }
70 | }
71 | }
72 |
73 | private (IMediator mediator, OutboxDbContext dbContext) GetServices(IServiceScope scope)
74 | {
75 | var outBoxDbContext = scope.ServiceProvider.GetService();
76 | if (outBoxDbContext == null)
77 | throw new ArgumentNullException(nameof(OutboxDbContext), "Cant resolve OutboxDbContext from service provider");
78 |
79 | var mediator = scope.ServiceProvider.GetService();
80 | if (mediator == null)
81 | throw new ArgumentNullException(nameof(IMediator), "Cant resolve IMediator from service provider");
82 |
83 | return (mediator, outBoxDbContext);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/Startup/UsersModuleStartup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using MediatR;
5 | using Microsoft.AspNetCore.Authentication.JwtBearer;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.IdentityModel.Tokens;
11 | using ModularMonolith.User.Application;
12 | using ModularMonolith.User.Application.Commands.Register;
13 | using ModularMonolith.User.Application.Entities;
14 | using ModularMonolith.User.Application.Interfaces;
15 | using ModularMonolith.User.Application.Queries.GetUserDetails;
16 | using ModularMonolith.User.Contracts;
17 | using ModularMonolith.User.Infrastructure.Services;
18 | using Refit;
19 |
20 | namespace ModularMonolith.User.Infrastructure.Startup
21 | {
22 | public static class UsersModuleStartup
23 | {
24 | public static IServiceCollection AddUserModule(
25 | this IServiceCollection services, IConfiguration configuration)
26 | {
27 | services.AddMediatR(typeof(RegisterUserCommand));
28 |
29 | services.AddDbContext(x =>
30 | {
31 | var connectionString = configuration["Modules:UsersModule:DbConnectionString"];
32 | x.UseSqlServer(connectionString);
33 | });
34 | services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true)
35 | .AddEntityFrameworkStores();
36 |
37 | services
38 | .AddRefitClient()
39 | .ConfigureHttpClient(c =>
40 | {
41 | var userModuleUrl = configuration["Modules:UsersModule:Url"];
42 | c.BaseAddress = new Uri(userModuleUrl);
43 | });
44 |
45 | services.AddScoped();
46 |
47 | services.Configure(options =>
48 | {
49 | // Password settings.
50 | options.Password.RequireDigit = true;
51 | options.Password.RequireLowercase = true;
52 | options.Password.RequireNonAlphanumeric = true;
53 | options.Password.RequireUppercase = true;
54 | options.Password.RequiredLength = 6;
55 | options.Password.RequiredUniqueChars = 1;
56 |
57 | // Lockout settings.
58 | options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
59 | options.Lockout.MaxFailedAccessAttempts = 5;
60 | options.Lockout.AllowedForNewUsers = true;
61 |
62 | // AppUser settings.
63 | options.User.AllowedUserNameCharacters =
64 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
65 | options.User.RequireUniqueEmail = false;
66 | });
67 |
68 | services.AddAuthentication(options =>
69 | {
70 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
71 | options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
72 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
73 | }).AddJwtBearer(jwt =>
74 | {
75 | var key = Encoding.ASCII.GetBytes(configuration["Jwt:Secret"]);
76 | var issuer = configuration["Jwt:Issuer"];
77 | jwt.SaveToken = true;
78 | jwt.TokenValidationParameters = new TokenValidationParameters
79 | {
80 | ValidateIssuerSigningKey = true,
81 | IssuerSigningKey = new SymmetricSecurityKey(key),
82 | ValidateIssuer = true,
83 | ValidIssuer = issuer,
84 | ValidAudiences = new List() { issuer },
85 | ValidateAudience = true,
86 | RequireExpirationTime = false,
87 | ValidateLifetime = true
88 | };
89 | });
90 |
91 |
92 | services.AddScoped();
93 |
94 | return services;
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Migrations/20230809170122_ExtendProductEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | #nullable disable
5 |
6 | namespace ModularMonolith.Products.Infrastructure.Migrations
7 | {
8 | ///
9 | public partial class ExtendProductEntity : Migration
10 | {
11 | ///
12 | protected override void Up(MigrationBuilder migrationBuilder)
13 | {
14 | migrationBuilder.EnsureSchema(
15 | name: "out");
16 |
17 | migrationBuilder.RenameColumn(
18 | name: "CreatedOn",
19 | schema: "pr",
20 | table: "Products",
21 | newName: "ModifiedDate");
22 |
23 | migrationBuilder.AlterColumn(
24 | name: "CreatedBy",
25 | schema: "pr",
26 | table: "Products",
27 | type: "nvarchar(100)",
28 | maxLength: 100,
29 | nullable: false,
30 | defaultValue: "",
31 | oldClrType: typeof(string),
32 | oldType: "nvarchar(max)",
33 | oldNullable: true);
34 |
35 | migrationBuilder.AddColumn(
36 | name: "Color",
37 | schema: "pr",
38 | table: "Products",
39 | type: "nvarchar(max)",
40 | nullable: false,
41 | defaultValue: "");
42 |
43 | migrationBuilder.AddColumn(
44 | name: "CreatedDate",
45 | schema: "pr",
46 | table: "Products",
47 | type: "datetime2",
48 | nullable: false,
49 | defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
50 |
51 | migrationBuilder.AddColumn(
52 | name: "ModifiedBy",
53 | schema: "pr",
54 | table: "Products",
55 | type: "nvarchar(max)",
56 | nullable: false,
57 | defaultValue: "");
58 |
59 | migrationBuilder.AddColumn(
60 | name: "Price_CurrencySymbol",
61 | schema: "pr",
62 | table: "Products",
63 | type: "int",
64 | nullable: true);
65 |
66 | migrationBuilder.AddColumn(
67 | name: "Price_Price",
68 | schema: "pr",
69 | table: "Products",
70 | type: "decimal(4,2)",
71 | precision: 4,
72 | nullable: true);
73 |
74 | }
75 |
76 | ///
77 | protected override void Down(MigrationBuilder migrationBuilder)
78 | {
79 | migrationBuilder.DropColumn(
80 | name: "Color",
81 | schema: "pr",
82 | table: "Products");
83 |
84 | migrationBuilder.DropColumn(
85 | name: "CreatedDate",
86 | schema: "pr",
87 | table: "Products");
88 |
89 | migrationBuilder.DropColumn(
90 | name: "ModifiedBy",
91 | schema: "pr",
92 | table: "Products");
93 |
94 | migrationBuilder.DropColumn(
95 | name: "Price_CurrencySymbol",
96 | schema: "pr",
97 | table: "Products");
98 |
99 | migrationBuilder.DropColumn(
100 | name: "Price_Price",
101 | schema: "pr",
102 | table: "Products");
103 |
104 | migrationBuilder.RenameColumn(
105 | name: "ModifiedDate",
106 | schema: "pr",
107 | table: "Products",
108 | newName: "CreatedOn");
109 |
110 | migrationBuilder.AlterColumn(
111 | name: "CreatedBy",
112 | schema: "pr",
113 | table: "Products",
114 | type: "nvarchar(max)",
115 | nullable: true,
116 | oldClrType: typeof(string),
117 | oldType: "nvarchar(100)",
118 | oldMaxLength: 100);
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Migrations/ProductDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
7 | using ModularMonolith.Products.Infrastructure;
8 |
9 | #nullable disable
10 |
11 | namespace ModularMonolith.Products.Infrastructure.Migrations
12 | {
13 | [DbContext(typeof(ProductsModuleDbContext))]
14 | partial class ProductDbContextModelSnapshot : ModelSnapshot
15 | {
16 | protected override void BuildModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasDefaultSchema("pr")
21 | .HasAnnotation("ProductVersion", "7.0.5")
22 | .HasAnnotation("Relational:MaxIdentifierLength", 128);
23 |
24 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
25 |
26 | modelBuilder.Entity("ModularMonolith.Outbox.Entities.OutBoxMessage", b =>
27 | {
28 | b.Property("Id")
29 | .ValueGeneratedOnAdd()
30 | .HasColumnType("uniqueidentifier");
31 |
32 | b.Property("ExecutedOn")
33 | .HasColumnType("datetime2");
34 |
35 | b.Property("Message")
36 | .HasColumnType("nvarchar(max)");
37 |
38 | b.Property("SavedOn")
39 | .HasColumnType("datetime2");
40 |
41 | b.Property("Type")
42 | .HasColumnType("nvarchar(max)");
43 |
44 | b.HasKey("Id");
45 |
46 | b.ToTable("OutBoxMessages", "out");
47 | });
48 |
49 | modelBuilder.Entity("ModularMonolith.Products.Domain.Entities.Product", b =>
50 | {
51 | b.Property("Id")
52 | .ValueGeneratedOnAdd()
53 | .HasColumnType("uniqueidentifier");
54 |
55 | b.Property("Color")
56 | .IsRequired()
57 | .HasColumnType("nvarchar(max)");
58 |
59 | b.Property("CreatedBy")
60 | .IsRequired()
61 | .HasMaxLength(100)
62 | .HasColumnType("nvarchar(100)");
63 |
64 | b.Property("CreatedDate")
65 | .HasColumnType("datetime2");
66 |
67 | b.Property("Description")
68 | .HasMaxLength(100)
69 | .HasColumnType("nvarchar(100)");
70 |
71 | b.Property("ModifiedBy")
72 | .IsRequired()
73 | .HasColumnType("nvarchar(max)");
74 |
75 | b.Property("ModifiedDate")
76 | .HasColumnType("datetime2");
77 |
78 | b.Property("Name")
79 | .HasMaxLength(100)
80 | .HasColumnType("nvarchar(100)");
81 |
82 | b.HasKey("Id");
83 |
84 | b.ToTable("Products", "pr");
85 | });
86 |
87 | modelBuilder.Entity("ModularMonolith.Products.Domain.Entities.Product", b =>
88 | {
89 | b.OwnsOne("ModularMonolith.Products.Domain.ValueObjects.Money", "Price", b1 =>
90 | {
91 | b1.Property("ProductId")
92 | .HasColumnType("uniqueidentifier");
93 |
94 | b1.Property("CurrencySymbol")
95 | .HasColumnType("int");
96 |
97 | b1.Property("Price")
98 | .HasPrecision(4)
99 | .HasColumnType("decimal(4,2)");
100 |
101 | b1.HasKey("ProductId");
102 |
103 | b1.ToTable("Products", "pr");
104 |
105 | b1.WithOwner()
106 | .HasForeignKey("ProductId");
107 | });
108 |
109 | b.Navigation("Price");
110 | });
111 | #pragma warning restore 612, 618
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Modules/Products/ModularMonolith.Products.Infrastructure/Migrations/20230809170122_ExtendProductEntity.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using ModularMonolith.Products.Infrastructure;
9 |
10 | #nullable disable
11 |
12 | namespace ModularMonolith.Products.Infrastructure.Migrations
13 | {
14 | [DbContext(typeof(ProductsModuleDbContext))]
15 | [Migration("20230809170122_ExtendProductEntity")]
16 | partial class ExtendProductEntity
17 | {
18 | ///
19 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
20 | {
21 | #pragma warning disable 612, 618
22 | modelBuilder
23 | .HasDefaultSchema("pr")
24 | .HasAnnotation("ProductVersion", "7.0.5")
25 | .HasAnnotation("Relational:MaxIdentifierLength", 128);
26 |
27 | SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
28 |
29 | modelBuilder.Entity("ModularMonolith.Outbox.Entities.OutBoxMessage", b =>
30 | {
31 | b.Property("Id")
32 | .ValueGeneratedOnAdd()
33 | .HasColumnType("uniqueidentifier");
34 |
35 | b.Property("ExecutedOn")
36 | .HasColumnType("datetime2");
37 |
38 | b.Property("Message")
39 | .HasColumnType("nvarchar(max)");
40 |
41 | b.Property("SavedOn")
42 | .HasColumnType("datetime2");
43 |
44 | b.Property("Type")
45 | .HasColumnType("nvarchar(max)");
46 |
47 | b.HasKey("Id");
48 |
49 | b.ToTable("OutBoxMessages", "out");
50 | });
51 |
52 | modelBuilder.Entity("ModularMonolith.Products.Domain.Entities.Product", b =>
53 | {
54 | b.Property("Id")
55 | .ValueGeneratedOnAdd()
56 | .HasColumnType("uniqueidentifier");
57 |
58 | b.Property("Color")
59 | .IsRequired()
60 | .HasColumnType("nvarchar(max)");
61 |
62 | b.Property("CreatedBy")
63 | .IsRequired()
64 | .HasMaxLength(100)
65 | .HasColumnType("nvarchar(100)");
66 |
67 | b.Property("CreatedDate")
68 | .HasColumnType("datetime2");
69 |
70 | b.Property("Description")
71 | .HasMaxLength(100)
72 | .HasColumnType("nvarchar(100)");
73 |
74 | b.Property("ModifiedBy")
75 | .IsRequired()
76 | .HasColumnType("nvarchar(max)");
77 |
78 | b.Property("ModifiedDate")
79 | .HasColumnType("datetime2");
80 |
81 | b.Property("Name")
82 | .HasMaxLength(100)
83 | .HasColumnType("nvarchar(100)");
84 |
85 | b.HasKey("Id");
86 |
87 | b.ToTable("Products", "pr");
88 | });
89 |
90 | modelBuilder.Entity("ModularMonolith.Products.Domain.Entities.Product", b =>
91 | {
92 | b.OwnsOne("ModularMonolith.Products.Domain.ValueObjects.Money", "Price", b1 =>
93 | {
94 | b1.Property("ProductId")
95 | .HasColumnType("uniqueidentifier");
96 |
97 | b1.Property("CurrencySymbol")
98 | .HasColumnType("int");
99 |
100 | b1.Property("Price")
101 | .HasPrecision(4)
102 | .HasColumnType("decimal(4,2)");
103 |
104 | b1.HasKey("ProductId");
105 |
106 | b1.ToTable("Products", "pr");
107 |
108 | b1.WithOwner()
109 | .HasForeignKey("ProductId");
110 | });
111 |
112 | b.Navigation("Price");
113 | });
114 | #pragma warning restore 612, 618
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/Migrations/20211022102431_InitAuth.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace ModularMonolith.User.Infrastructure.Migrations
5 | {
6 | public partial class InitAuth : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "AspNetRoles",
12 | columns: table => new
13 | {
14 | Id = table.Column(type: "nvarchar(450)", nullable: false),
15 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
16 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
17 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true)
18 | },
19 | constraints: table =>
20 | {
21 | table.PrimaryKey("PK_AspNetRoles", x => x.Id);
22 | });
23 |
24 | migrationBuilder.CreateTable(
25 | name: "AspNetUsers",
26 | columns: table => new
27 | {
28 | Id = table.Column(type: "nvarchar(450)", nullable: false),
29 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
30 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
31 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
32 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
33 | EmailConfirmed = table.Column(type: "bit", nullable: false),
34 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true),
35 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true),
36 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true),
37 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true),
38 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false),
39 | TwoFactorEnabled = table.Column(type: "bit", nullable: false),
40 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true),
41 | LockoutEnabled = table.Column(type: "bit", nullable: false),
42 | AccessFailedCount = table.Column(type: "int", nullable: false)
43 | },
44 | constraints: table =>
45 | {
46 | table.PrimaryKey("PK_AspNetUsers", x => x.Id);
47 | });
48 |
49 | migrationBuilder.CreateTable(
50 | name: "AspNetRoleClaims",
51 | columns: table => new
52 | {
53 | Id = table.Column(type: "int", nullable: false)
54 | .Annotation("SqlServer:Identity", "1, 1"),
55 | RoleId = table.Column(type: "nvarchar(450)", nullable: false),
56 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true),
57 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true)
58 | },
59 | constraints: table =>
60 | {
61 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
62 | table.ForeignKey(
63 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
64 | column: x => x.RoleId,
65 | principalTable: "AspNetRoles",
66 | principalColumn: "Id",
67 | onDelete: ReferentialAction.Cascade);
68 | });
69 |
70 | migrationBuilder.CreateTable(
71 | name: "AspNetUserClaims",
72 | columns: table => new
73 | {
74 | Id = table.Column(type: "int", nullable: false)
75 | .Annotation("SqlServer:Identity", "1, 1"),
76 | UserId = table.Column(type: "nvarchar(450)", nullable: false),
77 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true),
78 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true)
79 | },
80 | constraints: table =>
81 | {
82 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
83 | table.ForeignKey(
84 | name: "FK_AspNetUserClaims_AspNetUsers_UserId",
85 | column: x => x.UserId,
86 | principalTable: "AspNetUsers",
87 | principalColumn: "Id",
88 | onDelete: ReferentialAction.Cascade);
89 | });
90 |
91 | migrationBuilder.CreateTable(
92 | name: "AspNetUserLogins",
93 | columns: table => new
94 | {
95 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false),
96 | ProviderKey = table.Column(type: "nvarchar(450)", nullable: false),
97 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true),
98 | UserId = table.Column(type: "nvarchar(450)", nullable: false)
99 | },
100 | constraints: table =>
101 | {
102 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
103 | table.ForeignKey(
104 | name: "FK_AspNetUserLogins_AspNetUsers_UserId",
105 | column: x => x.UserId,
106 | principalTable: "AspNetUsers",
107 | principalColumn: "Id",
108 | onDelete: ReferentialAction.Cascade);
109 | });
110 |
111 | migrationBuilder.CreateTable(
112 | name: "AspNetUserRoles",
113 | columns: table => new
114 | {
115 | UserId = table.Column(type: "nvarchar(450)", nullable: false),
116 | RoleId = table.Column(type: "nvarchar(450)", nullable: false)
117 | },
118 | constraints: table =>
119 | {
120 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
121 | table.ForeignKey(
122 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
123 | column: x => x.RoleId,
124 | principalTable: "AspNetRoles",
125 | principalColumn: "Id",
126 | onDelete: ReferentialAction.Cascade);
127 | table.ForeignKey(
128 | name: "FK_AspNetUserRoles_AspNetUsers_UserId",
129 | column: x => x.UserId,
130 | principalTable: "AspNetUsers",
131 | principalColumn: "Id",
132 | onDelete: ReferentialAction.Cascade);
133 | });
134 |
135 | migrationBuilder.CreateTable(
136 | name: "AspNetUserTokens",
137 | columns: table => new
138 | {
139 | UserId = table.Column(type: "nvarchar(450)", nullable: false),
140 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false),
141 | Name = table.Column(type: "nvarchar(450)", nullable: false),
142 | Value = table.Column(type: "nvarchar(max)", nullable: true)
143 | },
144 | constraints: table =>
145 | {
146 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
147 | table.ForeignKey(
148 | name: "FK_AspNetUserTokens_AspNetUsers_UserId",
149 | column: x => x.UserId,
150 | principalTable: "AspNetUsers",
151 | principalColumn: "Id",
152 | onDelete: ReferentialAction.Cascade);
153 | });
154 |
155 | migrationBuilder.CreateIndex(
156 | name: "IX_AspNetRoleClaims_RoleId",
157 | table: "AspNetRoleClaims",
158 | column: "RoleId");
159 |
160 | migrationBuilder.CreateIndex(
161 | name: "RoleNameIndex",
162 | table: "AspNetRoles",
163 | column: "NormalizedName",
164 | unique: true,
165 | filter: "[NormalizedName] IS NOT NULL");
166 |
167 | migrationBuilder.CreateIndex(
168 | name: "IX_AspNetUserClaims_UserId",
169 | table: "AspNetUserClaims",
170 | column: "UserId");
171 |
172 | migrationBuilder.CreateIndex(
173 | name: "IX_AspNetUserLogins_UserId",
174 | table: "AspNetUserLogins",
175 | column: "UserId");
176 |
177 | migrationBuilder.CreateIndex(
178 | name: "IX_AspNetUserRoles_RoleId",
179 | table: "AspNetUserRoles",
180 | column: "RoleId");
181 |
182 | migrationBuilder.CreateIndex(
183 | name: "EmailIndex",
184 | table: "AspNetUsers",
185 | column: "NormalizedEmail");
186 |
187 | migrationBuilder.CreateIndex(
188 | name: "UserNameIndex",
189 | table: "AspNetUsers",
190 | column: "NormalizedUserName",
191 | unique: true,
192 | filter: "[NormalizedUserName] IS NOT NULL");
193 | }
194 |
195 | protected override void Down(MigrationBuilder migrationBuilder)
196 | {
197 | migrationBuilder.DropTable(
198 | name: "AspNetRoleClaims");
199 |
200 | migrationBuilder.DropTable(
201 | name: "AspNetUserClaims");
202 |
203 | migrationBuilder.DropTable(
204 | name: "AspNetUserLogins");
205 |
206 | migrationBuilder.DropTable(
207 | name: "AspNetUserRoles");
208 |
209 | migrationBuilder.DropTable(
210 | name: "AspNetUserTokens");
211 |
212 | migrationBuilder.DropTable(
213 | name: "AspNetRoles");
214 |
215 | migrationBuilder.DropTable(
216 | name: "AspNetUsers");
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/Modules/User/ModularMonolith.User.Infrastructure/Migrations/20211022102431_InitAuth.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Metadata;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 | using ModularMonolith.User.Infrastructure;
9 |
10 | namespace ModularMonolith.User.Infrastructure.Migrations
11 | {
12 | [DbContext(typeof(UsersDbContext))]
13 | [Migration("20211022102431_InitAuth")]
14 | partial class InitAuth
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
21 | .HasAnnotation("ProductVersion", "5.0.5")
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
25 | {
26 | b.Property("Id")
27 | .HasColumnType("nvarchar(450)");
28 |
29 | b.Property("ConcurrencyStamp")
30 | .IsConcurrencyToken()
31 | .HasColumnType("nvarchar(max)");
32 |
33 | b.Property("Name")
34 | .HasMaxLength(256)
35 | .HasColumnType("nvarchar(256)");
36 |
37 | b.Property("NormalizedName")
38 | .HasMaxLength(256)
39 | .HasColumnType("nvarchar(256)");
40 |
41 | b.HasKey("Id");
42 |
43 | b.HasIndex("NormalizedName")
44 | .IsUnique()
45 | .HasDatabaseName("RoleNameIndex")
46 | .HasFilter("[NormalizedName] IS NOT NULL");
47 |
48 | b.ToTable("AspNetRoles");
49 | });
50 |
51 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
52 | {
53 | b.Property("Id")
54 | .ValueGeneratedOnAdd()
55 | .HasColumnType("int")
56 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
57 |
58 | b.Property("ClaimType")
59 | .HasColumnType("nvarchar(max)");
60 |
61 | b.Property("ClaimValue")
62 | .HasColumnType("nvarchar(max)");
63 |
64 | b.Property("RoleId")
65 | .IsRequired()
66 | .HasColumnType("nvarchar(450)");
67 |
68 | b.HasKey("Id");
69 |
70 | b.HasIndex("RoleId");
71 |
72 | b.ToTable("AspNetRoleClaims");
73 | });
74 |
75 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
76 | {
77 | b.Property("Id")
78 | .HasColumnType("nvarchar(450)");
79 |
80 | b.Property("AccessFailedCount")
81 | .HasColumnType("int");
82 |
83 | b.Property("ConcurrencyStamp")
84 | .IsConcurrencyToken()
85 | .HasColumnType("nvarchar(max)");
86 |
87 | b.Property("Email")
88 | .HasMaxLength(256)
89 | .HasColumnType("nvarchar(256)");
90 |
91 | b.Property("EmailConfirmed")
92 | .HasColumnType("bit");
93 |
94 | b.Property("LockoutEnabled")
95 | .HasColumnType("bit");
96 |
97 | b.Property("LockoutEnd")
98 | .HasColumnType("datetimeoffset");
99 |
100 | b.Property("NormalizedEmail")
101 | .HasMaxLength(256)
102 | .HasColumnType("nvarchar(256)");
103 |
104 | b.Property("NormalizedUserName")
105 | .HasMaxLength(256)
106 | .HasColumnType("nvarchar(256)");
107 |
108 | b.Property("PasswordHash")
109 | .HasColumnType("nvarchar(max)");
110 |
111 | b.Property("PhoneNumber")
112 | .HasColumnType("nvarchar(max)");
113 |
114 | b.Property("PhoneNumberConfirmed")
115 | .HasColumnType("bit");
116 |
117 | b.Property("SecurityStamp")
118 | .HasColumnType("nvarchar(max)");
119 |
120 | b.Property("TwoFactorEnabled")
121 | .HasColumnType("bit");
122 |
123 | b.Property("UserName")
124 | .HasMaxLength(256)
125 | .HasColumnType("nvarchar(256)");
126 |
127 | b.HasKey("Id");
128 |
129 | b.HasIndex("NormalizedEmail")
130 | .HasDatabaseName("EmailIndex");
131 |
132 | b.HasIndex("NormalizedUserName")
133 | .IsUnique()
134 | .HasDatabaseName("UserNameIndex")
135 | .HasFilter("[NormalizedUserName] IS NOT NULL");
136 |
137 | b.ToTable("AspNetUsers");
138 | });
139 |
140 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
141 | {
142 | b.Property