Merit Journal is a full-stack application that allows users to record and manage meritorious acts or significant things done on a particular day. The backend is built with C# and ASP.NET Core, following Clean Architecture principles.
The backend follows Clean Architecture with distinct layers:
- Domain Layer (
MeritJournal.Domain): Contains entities, enums, value objects, and domain exceptions. - Application Layer (
MeritJournal.Application): Contains business logic, interfaces, DTOs, and CQRS implementation with MediatR. - Infrastructure Layer (
MeritJournal.Infrastructure): Contains implementations of interfaces defined in the application layer, including database access via EF Core. - Presentation Layer (
MeritJournal.API): Contains API endpoints, controllers, and configuration for the web services.
The backend uses the Repository pattern for data access abstraction. This decouples the application from specific data access technologies.
- IRepository: Generic interface for CRUD operations on entities.
- Repository: Concrete implementation of
IRepository<T>using EF Core.
The Unit of Work pattern manages transactions and ensures atomic operations:
- IUnitOfWork: Interface that defines the contract for unit of work.
- UnitOfWork: Implementation that coordinates the work of multiple repositories.
The Command Query Responsibility Segregation pattern is implemented using MediatR:
- Commands: For operations that change state (Create, Update, Delete).
- Queries: For operations that retrieve data without changing state.
- PostgreSQL: The database engine used for persistent storage.
- Entity Framework Core: ORM used for database interactions.
- Migrations: Used for database schema versioning and updates.
src/
Backend/
MeritJournal.Domain/
Entities/ # Domain entities
MeritJournal.Application/
DTOs/ # Data transfer objects
Features/ # CQRS commands and queries
Interfaces/ # Interfaces for infrastructure implementations
MeritJournal.Infrastructure/
Persistence/ # Database context and configurations
Repositories/ # Repository implementations
DependencyInjection.cs # Infrastructure service registrations
MeritJournal.API/
Endpoints/ # API endpoint configurations
Configuration/ # API-specific configurations
tests/
MeritJournal.UnitTests/ # Unit tests for application logic
MeritJournal.IntegrationTests/ # Integration tests for API and database
JournalEntry.cs: Core entity representing a journal entry.JournalImage.cs: Entity representing an image associated with a journal entry.Tag.cs: Entity for categorizing journal entries.JournalEntryTag.cs: Join entity connecting journal entries and tags.
IRepository.cs: Interface for generic repository operations.IUnitOfWork.cs: Interface for unit of work operations.- Various DTOs in
DTOs/folder. - CQRS handlers in
Features/folder.
ApplicationDbContext.cs: EF Core database context.Repository.cs: Generic repository implementation.UnitOfWork.cs: Implementation of the unit of work interface.DependencyInjection.cs: Service registrations.
Program.cs: Application startup and configuration.JournalEntryEndpoints.cs: API endpoints for journal entries.
The backend uses JWT Bearer token authentication with OIDC providers:
- Users are identified by their OIDC provider's sub claim.
- JWTs are validated using Microsoft.AspNetCore.Authentication.JwtBearer.
- User IDs from the JWT are used to filter journal entries for the current user.
-
Repository Refactoring: The repository pattern has been fully implemented with
Repository<T>class:- Removed
EfRepository<T>which was no longer needed Repository<T>now fully implements theIRepository<T>interface- Both sync and async operations are supported
- Removed
-
Unit of Work Pattern: All data operations are now done through the Unit of Work:
UnitOfWorkclass provides access to individual entity repositories- Ensures consistent transaction management
-
Dependency Injection:
- Only
IUnitOfWorkis registered in the DI container - Removed
IApplicationDbContextas it's no longer needed
- Only
-
Nullable Reference Types:
- The project uses C# nullable reference types
- Be careful with collections like
ImagesandTagsin DTOs, which can be null
-
Testing:
- Unit tests use mock repositories and unit of work
- Ensure proper null-checking in test assertions
- Create the entity class in
MeritJournal.Domain/Entities/ - Update
ApplicationDbContext.csto include aDbSet<T>for the new entity - Add a repository property in
IUnitOfWorkand implement it inUnitOfWork - Create migrations:
dotnet ef migrations add AddNewEntity - Apply migrations:
dotnet ef database update
- Create DTOs in
MeritJournal.Application/DTOs/if needed - Create Command/Query classes in
MeritJournal.Application/Features/ - Implement Command/Query handlers
- Add endpoints in the appropriate endpoint configuration file or create a new one
dotnet ef migrations add <MigrationName> -p ..\MeritJournal.Infrastructure -s ..\MeritJournal.API
dotnet ef database update -p ..\MeritJournal.Infrastructure -s ..\MeritJournal.API
dotnet test src/Backend/tests/MeritJournal.UnitTests
dotnet test src/Backend/tests/MeritJournal.IntegrationTests
When running PowerShell commands for this project, use semicolons (;) for command chaining instead of ampersands (&&). This ensures compatibility with PowerShell's syntax.
# Correct (using semicolons)
cd src/Backend; dotnet build; dotnet run
# Avoid (using &&)
cd src/Backend && dotnet build && dotnet run# Building the solution
cd src/Backend; dotnet build
# Running the API
cd src/Backend/MeritJournal.API; dotnet run
# Creating and applying migrations
cd src/Backend; dotnet ef migrations add <MigrationName> -p MeritJournal.Infrastructure -s MeritJournal.API; dotnet ef database update -p MeritJournal.Infrastructure -s MeritJournal.API
# Running all tests
cd src/Backend; dotnet test- Missing interface implementations: Ensure concrete classes fully implement their interfaces.
- Null reference exceptions: Check for proper null handling, especially with nullable reference types.
- Entity Framework errors: Ensure migrations are up-to-date and the database connection is valid.
- Use
IUnitOfWorkfor all data access operations - Prefer async methods when available
- Always validate user input at the API layer
- Use the CQRS pattern for new features
- Follow Clean Architecture principles: domain entities should not depend on infrastructure