# Backend Architecture Guide ## 🏗️ Clean Architecture Overview The backend follows **Clean Architecture** with strict layer separation: ``` ┌─────────────────────────────────────────────────────────────┐ │ API LAYER (Controllers) │ │ - HTTP Request/Response │ │ - Authentication & Authorization │ │ - Exception Handling │ │ - Input Validation (basic) │ └─────────────────┬───────────────────────────────────────────┘ │ Calls ▼ ┌─────────────────────────────────────────────────────────────┐ │ SERVICE LAYER (Business Logic) │ │ - Business Rules │ │ - FluentValidation (detailed validation) │ │ - Data Transformation │ │ - Orchestration │ └─────────────────┬───────────────────────────────────────────┘ │ Calls ▼ ┌─────────────────────────────────────────────────────────────┐ │ DATA SERVICE LAYER (Persistence) │ │ - Database Access │ │ - Entity Operations (CRUD) │ │ - Query Building │ │ - Transaction Management │ └─────────────────┬───────────────────────────────────────────┘ │ Uses ▼ ┌─────────────────────────────────────────────────────────────┐ │ DATA LAYER (EF Core) │ │ - DbContext │ │ - Entity Models │ │ - Database Schema │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 📁 Project Structure ``` backend/shoc-backend/ │ ├── Api.SeaHavenIndustries/ # 🌐 API Layer │ ├── Controllers/ # HTTP endpoints │ ├── DTOs/ # Data Transfer Objects (API contracts) │ ├── Program.cs # App startup & DI registration │ └── appsettings.json # Configuration │ ├── SeaHaven.Services/ # 💼 Service Layer (Business Logic) │ ├── Interfaces/ # Service contracts (IContactService, etc.) │ ├── Implementation/ # Service implementations │ ├── DTOs/ # Internal DTOs for service layer │ └── Validators/ # FluentValidation validators │ ├── SeaHaven.DataServices/ # 💾 Data Access Layer │ ├── Interfaces/ # DataService contracts (IContactDataService, etc.) │ ├── Implementation/ # DataService implementations │ └── DTOs/ # Data layer DTOs │ └── Data.SeaHavenIndustries/ # 🗄️ Data Layer (EF Core) ├── Models/ # Entity models (Contact, Employee, etc.) ├── ApplicationDbContext.cs # EF Core DbContext └── Migrations/ # Database migrations ``` --- ## 🔄 Data Flow Example (Creating a Contact) ### 1️⃣ **API Layer** (`ContactController.cs`) ```csharp [HttpPost("Create")] public async Task AddContacts(Contact_DTO dto) { try { // Get authenticated user ID var userId = _userManager.GetUserId(User); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); // Convert API DTO → Service DTO var serviceDto = dto.ToServiceCreateDTO(); // Call Service Layer await _contactService.CreateContactAsync(serviceDto, userId); return Ok(new Response { Status = "Success", Message = "Contact created" }); } catch (ValidationException vex) { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (KeyNotFoundException) { return NotFound(new Response { Status = "Error", Message = "Resource not found" }); } catch (Exception ex) { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } ``` **Responsibilities:** - ✅ Authenticate user - ✅ Extract user ID from claims - ✅ Map API DTO to Service DTO - ✅ Catch exceptions and return proper HTTP status codes - ❌ NO business logic - ❌ NO database access --- ### 2️⃣ **Service Layer** (`ContactService.cs`) ```csharp public class ContactService : IContactService { private readonly IContactDataService _dataService; private readonly IValidator _createValidator; public ContactService( IContactDataService dataService, IValidator createValidator) { _dataService = dataService; _createValidator = createValidator; } public async Task CreateContactAsync(CreateContactDTO dto, string userId) { // STEP 1: Validate using FluentValidation var validationResult = await _createValidator.ValidateAsync(dto); if (!validationResult.IsValid) { throw new ValidationException(validationResult.Errors); } // STEP 2: Business logic (if any) // Example: Check for duplicates, apply business rules, etc. // STEP 3: Map Service DTO → Data DTO var dataDto = new CreateContactDataDTO { FirstName = dto.FirstName, LastName = dto.LastName, Email = dto.Email, Phone = dto.Phone }; // STEP 4: Call Data Service Layer var createdContact = await _dataService.CreateAsync(dataDto, userId); // STEP 5: Map Data DTO → Service DTO and return return new ContactDTO { Id = createdContact.Id, FirstName = createdContact.FirstName, LastName = createdContact.LastName, Email = createdContact.Email, Phone = createdContact.Phone }; } } ``` **Responsibilities:** - ✅ Validate input using FluentValidation - ✅ Apply business rules - ✅ Orchestrate operations - ✅ Transform data between layers - ❌ NO HTTP concerns - ❌ NO direct database access --- ### 3️⃣ **Data Service Layer** (`ContactDataService.cs`) ```csharp public class ContactDataService : IContactDataService { private readonly ApplicationDbContext _db; public ContactDataService(ApplicationDbContext db) { _db = db; } public async Task CreateAsync(CreateContactDataDTO dto, string userId) { // STEP 1: Create entity from DTO var contact = new Contact { FirstName = dto.FirstName, LastName = dto.LastName, Email = dto.Email, Phone = dto.Phone, CreatedBy = userId, CreatedDate = DateTime.UtcNow, ModifiedBy = userId, ModifiedDate = DateTime.UtcNow }; // STEP 2: Add to database _db.Contacts.Add(contact); await _db.SaveChangesAsync(); // STEP 3: Map entity → Data DTO and return return new ContactDataDTO { Id = contact.Id, FirstName = contact.FirstName, LastName = contact.LastName, Email = contact.Email, Phone = contact.Phone }; } } ``` **Responsibilities:** - ✅ Direct database operations (CRUD) - ✅ Entity mapping - ✅ Set audit fields (CreatedBy, CreatedDate, etc.) - ✅ Transaction management - ❌ NO validation - ❌ NO business logic --- ## 🔌 Dependency Injection (DI) ### Automatic Assembly Scanning Services are **automatically registered** by scanning assemblies using naming convention. **In `Program.cs`:** ```csharp builder.Services.AddDataServices(); // Auto-register all Data Services builder.Services.AddBusinessServices(); // Auto-register all Business Services + Validators ``` ### How It Works **Module Files:** - `SeaHaven.DataServices/DependencyInjection/DataServicesModule.cs` - `SeaHaven.Services/DependencyInjection/ServicesModule.cs` **Process:** 1. Scans assembly for all classes 2. Finds interface matching pattern `I{ClassName}` 3. Registers as Scoped service 4. Also auto-registers FluentValidation validators ### Naming Convention Required | Interface | Implementation | Registered? | |-----------|---------------|-------------| | `IContactService` | `ContactService` | ✅ YES | | `IContactDataService` | `ContactDataService` | ✅ YES | | `ISomething` | `SomethingDifferent` | ❌ NO | **Rule**: Interface must be `I` + Class name exactly ### Usage in Code Once registered, inject via constructor: ```csharp public class ContactController : Controller { private readonly IContactService _contactService; public ContactController(IContactService contactService) { _contactService = contactService; } } ``` ### Benefits ✅ No manual registration - just follow naming convention ✅ Auto-discovery - new services work automatically ✅ FluentValidation auto-registered --- ## ✅ FluentValidation ### Validator Example (`CreateContactDTOValidator.cs`) ```csharp public class CreateContactDTOValidator : AbstractValidator { public CreateContactDTOValidator() { RuleFor(x => x.FirstName) .NotEmpty().WithMessage("First name is required") .MaximumLength(50).WithMessage("First name cannot exceed 50 characters"); RuleFor(x => x.LastName) .NotEmpty().WithMessage("Last name is required") .MaximumLength(50).WithMessage("Last name cannot exceed 50 characters"); RuleFor(x => x.Email) .NotEmpty().WithMessage("Email is required") .EmailAddress().WithMessage("Invalid email format") .MaximumLength(100).WithMessage("Email cannot exceed 100 characters"); RuleFor(x => x.Phone) .Matches(@"^\+?[1-9]\d{1,14}$").When(x => !string.IsNullOrEmpty(x.Phone)) .WithMessage("Invalid phone number format"); } } ``` ### Where Validation Happens ``` ❌ Controller → NO detailed validation (only catches ValidationException) ✅ Service → YES - FluentValidation runs here ❌ DataService → NO validation ``` --- ## 🎯 Interface Examples ### Service Interface (`IContactService.cs`) ```csharp public interface IContactService { Task CreateContactAsync(CreateContactDTO dto, string userId); Task UpdateContactAsync(int id, UpdateContactDTO dto, string userId); Task DeleteContactAsync(int id, string userId); Task GetContactByIdAsync(int id); Task> GetContactsPagedAsync(int page, int pageSize, string? search); } ``` ### Data Service Interface (`IContactDataService.cs`) ```csharp public interface IContactDataService { Task CreateAsync(CreateContactDataDTO dto, string userId); Task UpdateAsync(int id, UpdateContactDataDTO dto, string userId); Task DeleteAsync(int id, string userId); Task GetByIdAsync(int id); Task> GetPagedAsync(int page, int pageSize, string? search); } ``` --- ## 📦 DTO Types ### 1. **API DTOs** (`Api.SeaHavenIndustries/DTOs/`) - Used for HTTP requests/responses - Map to frontend JSON structure - Example: `Contact_DTO`, `EditContact_DTO` ### 2. **Service DTOs** (`SeaHaven.Services/DTOs/`) - Used between Controller ↔ Service - Business-focused structure - Example: `CreateContactDTO`, `UpdateContactDTO`, `ContactDTO` ### 3. **Data DTOs** (`SeaHaven.DataServices/DTOs/`) - Used between Service ↔ DataService - Database-focused structure - Example: `CreateContactDataDTO`, `ContactDataDTO` ### DTO Mapping Flow ``` API DTO → Service DTO → Data DTO → Entity (HTTP) (Business) (Data) (Database) ``` --- ## 🚨 Exception Handling Pattern ### Controller Exception Handling (STANDARD PATTERN) ```csharp [HttpPost("Create")] public async Task Create([FromBody] SomeDTO dto) { try { // Get user ID var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); // Call service await _service.CreateAsync(dto, userId); return Ok(new Response { Status = "Success", Message = "Created successfully" }); } catch (ValidationException vex) // ⚠️ CATCH THIS FIRST { var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage)); return BadRequest(new Response { Status = "Validation Error", Message = errors }); } catch (KeyNotFoundException) // ⚠️ THEN THIS { return NotFound(new Response { Status = "Error", Message = "Not found" }); } catch (Exception ex) // ⚠️ GENERIC LAST { return StatusCode(500, new Response { Status = "Error", Message = ex.Message }); } } ``` ### HTTP Status Code Mapping - **200 OK** → Success - **400 Bad Request** → Validation errors - **401 Unauthorized** → Not authenticated - **404 Not Found** → Resource not found - **500 Internal Server Error** → Unexpected errors --- ## 🔐 Authentication & User ID ### Getting User ID (Two Methods) **Method 1: Using Claims (Preferred)** ```csharp var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); ``` **Method 2: Using UserManager (for Identity)** ```csharp var userId = _userManager.GetUserId(User); if (userId == null) return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" }); ``` ### Audit Fields Every entity should track: - `CreatedBy` (string - User ID GUID) - `CreatedDate` (DateTime) - `ModifiedBy` (string - User ID GUID) - `ModifiedDate` (DateTime) --- ## 📋 Naming Conventions ### Controllers - Named after entity + "Controller" - Example: `ContactController`, `EmployeeController` ### Services - Interface: `I[Entity]Service` - Implementation: `[Entity]Service` - Example: `IContactService`, `ContactService` ### Data Services - Interface: `I[Entity]DataService` - Implementation: `[Entity]DataService` - Example: `IContactDataService`, `ContactDataService` ### DTOs - API Layer: `[Entity]_DTO`, `Edit[Entity]_DTO` - Service Layer: `Create[Entity]DTO`, `Update[Entity]DTO`, `[Entity]DTO` - Data Layer: `Create[Entity]DataDTO`, `[Entity]DataDTO` --- ## ✅ Best Practices Checklist ### Controllers - [ ] Use dependency injection for services - [ ] Get user ID from `User.FindFirstValue(ClaimTypes.NameIdentifier)` - [ ] Return 401 if userId is null - [ ] Catch `ValidationException` BEFORE generic `Exception` - [ ] Return appropriate HTTP status codes - [ ] NO business logic in controllers - [ ] NO direct database access (`_db`) ### Services - [ ] Validate using FluentValidation - [ ] Throw `ValidationException` for validation errors - [ ] Throw `KeyNotFoundException` for not found - [ ] Pass userId to DataService - [ ] Transform DTOs between layers - [ ] Implement business rules here ### Data Services - [ ] Direct database access only - [ ] Set audit fields (CreatedBy, CreatedDate, ModifiedBy, ModifiedDate) - [ ] Use async/await for all database operations - [ ] NO validation logic - [ ] NO business logic --- ## 🎓 Quick Reference | Layer | Purpose | Can Access | Cannot Access | |-------|---------|-----------|---------------| | **Controller** | HTTP handling, auth | Service layer | Database directly | | **Service** | Business logic, validation | DataService layer | Database directly | | **DataService** | Database operations | DbContext, Entities | HTTP context | | **Data** | Entity definitions | Nothing (POCOs) | Application logic | --- ## 📚 Example: Full CRUD Implementation See these files for complete examples: - **Contact**: `ContactController.cs`, `ContactService.cs`, `ContactDataService.cs` - **Employee**: `EmployeeController.cs`, `EmployeeService.cs`, `EmployeeDataService.cs` - **Asset**: `AssetController.cs`, `AssetService.cs`, `AssetDataService.cs` All follow the same Clean Architecture pattern! --- ## 🔐 Configuration & Secrets No secrets are stored in source. The committed `appsettings*.json` files hold only `${PLACEHOLDER}` tokens; real values must be supplied at runtime through one of the standard .NET configuration sources (later sources win): 1. `appsettings.json` / `appsettings.{Environment}.json` — committed, placeholders only. 2. **User Secrets** (local dev): `dotnet user-secrets set "Section:Key" "value"`. 3. **Environment variables** — use `__` (double underscore) as the section separator. In AWS Elastic Beanstalk these are the environment properties. See `.env.example` in the repo root for the full list. Required values: | Setting | Env-var form | Used by | Notes | |---|---|---|---| | `ConnectionStrings:DefaultConnection` | `ConnectionStrings__DefaultConnection` | Both projects | SQL Server connection string | | `SendGrid:ApiKey` | `SendGrid__ApiKey` | Both projects | Transactional email | | `JWT:Secret` | `JWT__Secret` | `Api.SeaHavenIndustries` | JWT signing key | | `GoogleMaps:ApiKey` | `GoogleMaps__ApiKey` | `SeaHavenIndustries` (Blazor) | Maps/Places; read in `App.razor` + `Home.razor` | ### AWS credentials `UploadFileHp` (S3 uploads) uses the **AWS SDK default credential chain** — it no longer hardcodes access keys. Credentials resolve from `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` env vars, the shared profile/SSO, or (preferred in production) the EC2/ECS instance role. The region comes from `AddDefaultAWSOptions` in `Program.cs`. > ⚠️ The secrets previously committed to this repo (DB password, SendGrid key, JWT secret, > Google Maps keys, AWS access key) should be considered compromised and **rotated**.