mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-04 00:53:19 +00:00
344 lines
11 KiB
Markdown
344 lines
11 KiB
Markdown
# Architecture At A Glance
|
|
|
|
## 📐 The 4-Layer Architecture
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 🌐 API LAYER (Controllers) │
|
|
│ - HTTP Endpoints │
|
|
│ - Authentication │
|
|
│ - Exception → HTTP Status Code mapping │
|
|
│ ❌ NO Business Logic │
|
|
│ ❌ NO Database Access │
|
|
└─────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ Calls
|
|
▼
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 💼 SERVICE LAYER (Business Logic) │
|
|
│ - FluentValidation │
|
|
│ - Business Rules │
|
|
│ - Orchestration │
|
|
│ - DTO Transformations │
|
|
│ ❌ NO HTTP Concerns │
|
|
│ ❌ NO Database Access │
|
|
└─────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ Calls
|
|
▼
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 💾 DATA SERVICE LAYER (Persistence) │
|
|
│ - CRUD Operations │
|
|
│ - Database Queries │
|
|
│ - Audit Field Management │
|
|
│ ❌ NO Validation │
|
|
│ ❌ NO Business Logic │
|
|
└─────────────────┬────────────────────────────────────┘
|
|
│
|
|
│ Uses
|
|
▼
|
|
┌──────────────────────────────────────────────────────┐
|
|
│ 🗄️ DATA LAYER (EF Core + Entities) │
|
|
│ - DbContext │
|
|
│ - Entity Models (POCOs) │
|
|
│ - Migrations │
|
|
└──────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Responsibilities Matrix
|
|
|
|
| Layer | What It Does | What It Doesn't Do |
|
|
|-------|--------------|-------------------|
|
|
| **Controller** | • Handle HTTP requests<br>• Authenticate users<br>• Map API DTOs<br>• Catch exceptions<br>• Return HTTP status codes | • Business logic<br>• Validation rules<br>• Database queries |
|
|
| **Service** | • Validate data (FluentValidation)<br>• Apply business rules<br>• Coordinate operations<br>• Transform data | • HTTP concerns<br>• Database operations |
|
|
| **DataService** | • Execute database queries<br>• CRUD operations<br>• Set audit fields | • Validation<br>• Business rules |
|
|
| **Data** | • Define entities<br>• Database schema | • Application logic |
|
|
|
|
---
|
|
|
|
## 🔄 Request Flow
|
|
|
|
### Creating a Contact (Example)
|
|
|
|
```
|
|
1. HTTP POST /api/Contact/Create
|
|
↓
|
|
2. ContactController.Create()
|
|
- Checks authentication
|
|
- Gets userId from claims
|
|
↓
|
|
3. ContactService.CreateContactAsync()
|
|
- Validates using FluentValidation
|
|
- Applies business rules
|
|
↓
|
|
4. ContactDataService.CreateAsync()
|
|
- Creates entity
|
|
- Sets audit fields
|
|
- Saves to database
|
|
↓
|
|
5. Returns through layers:
|
|
Entity → DataDTO → ServiceDTO → ApiDTO → HTTP Response
|
|
```
|
|
|
|
---
|
|
|
|
## 📦 DTO Flow
|
|
|
|
```
|
|
Frontend (JSON)
|
|
↓
|
|
API DTO (Contact_DTO) ← Controller receives this
|
|
↓
|
|
Service DTO (CreateContactDTO) ← Service validates this
|
|
↓
|
|
Data DTO (CreateContactDataDTO) ← DataService uses this
|
|
↓
|
|
Entity (Contact) ← Database stores this
|
|
```
|
|
|
|
---
|
|
|
|
## 🔌 Dependency Injection
|
|
|
|
### Automatic Assembly Scanning
|
|
|
|
Services auto-register via assembly scanning (no manual registration needed).
|
|
|
|
```csharp
|
|
// Program.cs
|
|
builder.Services.AddDataServices(); // Auto-register data services
|
|
builder.Services.AddBusinessServices(); // Auto-register business services + validators
|
|
```
|
|
|
|
### Naming Convention (REQUIRED)
|
|
|
|
| Interface | Implementation | Registered? |
|
|
|-----------|---------------|-------------|
|
|
| `IContactService` | `ContactService` | ✅ YES |
|
|
| `ISomething` | `SomethingElse` | ❌ NO |
|
|
|
|
**Rule**: Interface = `I` + Class name exactly
|
|
|
|
---
|
|
|
|
## ✅ Validation
|
|
|
|
### Where and How
|
|
|
|
```
|
|
❌ Controller → Catches ValidationException
|
|
✅ Service → Runs FluentValidation.Validate()
|
|
❌ DataService → No validation
|
|
```
|
|
|
|
### FluentValidation Example
|
|
|
|
```csharp
|
|
public class CreateContactDTOValidator : AbstractValidator<CreateContactDTO>
|
|
{
|
|
public CreateContactDTOValidator()
|
|
{
|
|
RuleFor(x => x.FirstName)
|
|
.NotEmpty().WithMessage("First name is required")
|
|
.MaximumLength(50);
|
|
|
|
RuleFor(x => x.Email)
|
|
.EmailAddress().WithMessage("Invalid email");
|
|
}
|
|
}
|
|
```
|
|
|
|
### In Service
|
|
|
|
```csharp
|
|
var validationResult = await _validator.ValidateAsync(dto);
|
|
if (!validationResult.IsValid)
|
|
{
|
|
throw new ValidationException(validationResult.Errors);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚨 Exception Handling
|
|
|
|
### Standard Pattern (All Controllers)
|
|
|
|
```csharp
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(...);
|
|
|
|
await _service.SomeMethod(dto, userId);
|
|
return Ok(...);
|
|
}
|
|
catch (ValidationException vex) // 400 Bad Request
|
|
{
|
|
return BadRequest(...);
|
|
}
|
|
catch (KeyNotFoundException) // 404 Not Found
|
|
{
|
|
return NotFound(...);
|
|
}
|
|
catch (Exception ex) // 500 Internal Server Error
|
|
{
|
|
return StatusCode(500, ...);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 Authentication
|
|
|
|
### Getting User ID
|
|
|
|
```csharp
|
|
// Method 1: Claims (Preferred)
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
// Method 2: UserManager (for Identity)
|
|
var userId = _userManager.GetUserId(User);
|
|
|
|
// Always check for null
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
```
|
|
|
|
### Audit Fields
|
|
|
|
Every entity has:
|
|
```csharp
|
|
public string CreatedBy { get; set; } // User ID (GUID)
|
|
public DateTime CreatedDate { get; set; } // UTC timestamp
|
|
public string ModifiedBy { get; set; } // User ID (GUID)
|
|
public DateTime ModifiedDate { get; set; } // UTC timestamp
|
|
```
|
|
|
|
Set in DataService:
|
|
```csharp
|
|
CreatedBy = userId,
|
|
CreatedDate = DateTime.UtcNow,
|
|
ModifiedBy = userId,
|
|
ModifiedDate = DateTime.UtcNow
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 Naming Conventions
|
|
|
|
| Component | Pattern | Example |
|
|
|-----------|---------|---------|
|
|
| Entity | `[Entity]` | `Contact` |
|
|
| Controller | `[Entity]Controller` | `ContactController` |
|
|
| Service Interface | `I[Entity]Service` | `IContactService` |
|
|
| Service Class | `[Entity]Service` | `ContactService` |
|
|
| DataService Interface | `I[Entity]DataService` | `IContactDataService` |
|
|
| DataService Class | `[Entity]DataService` | `ContactDataService` |
|
|
| API DTO | `[Entity]_DTO` | `Contact_DTO` |
|
|
| Service DTO | `[Action][Entity]DTO` | `CreateContactDTO` |
|
|
| Data DTO | `[Action][Entity]DataDTO` | `CreateContactDataDTO` |
|
|
| Validator | `[DTO]Validator` | `CreateContactDTOValidator` |
|
|
|
|
---
|
|
|
|
## 🎨 Code Templates
|
|
|
|
### Controller Method Template
|
|
```csharp
|
|
[HttpPost("ActionName")]
|
|
public async Task<IActionResult> ActionName([FromBody] SomeDTO dto)
|
|
{
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (userId == null)
|
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
|
|
|
await _service.Method(dto, userId);
|
|
|
|
return Ok(new Response { Status = "Success", Message = "Success message" });
|
|
}
|
|
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 = "Not found" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
|
}
|
|
}
|
|
```
|
|
|
|
### Service Method Template
|
|
```csharp
|
|
public async Task<ResultDTO> MethodAsync(InputDTO dto, string userId)
|
|
{
|
|
// 1. Validate
|
|
var validationResult = await _validator.ValidateAsync(dto);
|
|
if (!validationResult.IsValid)
|
|
{
|
|
throw new ValidationException(validationResult.Errors);
|
|
}
|
|
|
|
// 2. Business logic (if any)
|
|
|
|
// 3. Call DataService
|
|
var result = await _dataService.MethodAsync(dataDto, userId);
|
|
|
|
// 4. Map and return
|
|
return MapToServiceDTO(result);
|
|
}
|
|
```
|
|
|
|
### DataService Method Template
|
|
```csharp
|
|
public async Task<DataDTO> MethodAsync(InputDataDTO dto, string userId)
|
|
{
|
|
// 1. Create/modify entity
|
|
var entity = new Entity
|
|
{
|
|
Property = dto.Property,
|
|
CreatedBy = userId,
|
|
CreatedDate = DateTime.UtcNow,
|
|
ModifiedBy = userId,
|
|
ModifiedDate = DateTime.UtcNow
|
|
};
|
|
|
|
// 2. Database operation
|
|
_db.Entities.Add(entity);
|
|
await _db.SaveChangesAsync();
|
|
|
|
// 3. Map and return
|
|
return MapToDataDTO(entity);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 Key Rules
|
|
|
|
1. **Never skip layers** - Always go Controller → Service → DataService
|
|
2. **No direct `_db` in Controllers** - Database access only in DataService
|
|
3. **Validate in Service** - Use FluentValidation, not manual checks
|
|
4. **Always check userId** - Return 401 if null
|
|
5. **Catch ValidationException first** - Before generic Exception
|
|
6. **Use interfaces everywhere** - For dependency injection
|
|
7. **Set audit fields** - CreatedBy, CreatedDate, ModifiedBy, ModifiedDate
|
|
8. **Return proper HTTP codes** - 200, 400, 401, 404, 500
|
|
|
|
---
|
|
|
|
## 🎓 Learn More
|
|
|
|
- **Full Details**: See `BACKEND_ARCHITECTURE.md`
|
|
- **Step-by-Step Guide**: See `QUICK_START_GUIDE.md`
|
|
- **Working Examples**: Look at `ContactController`, `ContactService`, `ContactDataService`
|