Architektura systemu Masaku¶
Wprowadzenie¶
System Masaku składa się z dwóch głównych komponentów: - masaku-portal - Frontend (Angular 14 + Nx) - masaku-api - Backend (.NET C# + Entity Framework)
Architektura high-level¶
┌─────────────────────────────────────────────────────────────┐
│ Użytkownicy │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Azure AD B2C (Autentykacja) │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ masaku-portal (SPA) │ │ masaku-api (.NET) │
│ - Angular 14 │ │ - ASP.NET Core │
│ - Nx Monorepo │ │ - RESTful API │
│ - NGXS State │◄──┤ - Entity Framework │
│ - Azure Blob Storage │ │ - SQL Server │
└───────────────────────────┘ └───────────────────────────┘
│
┌─────────────────────────┼─────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌────────────┐
│ SQL Server │ │ Azure Storage │ │ MailHog │
│ (Database) │ │ (Files/Blobs) │ │ (Email) │
└─────────────────┘ └──────────────────┘ └────────────┘
Architektura frontendowa (masaku-portal)¶
Struktura Nx Monorepo¶
Portal wykorzystuje Nx workspace z 32+ bibliotekami zorganizowanymi według domeny:
masaku-portal/
├── apps/
│ └── masaku/ # Główna aplikacja Angular
└── projects/ # Biblioteki (32+)
├── common/ # Współdzielone utilities
├── types/ # TypeScript interfaces
├── budgets/ # Moduł budżetów
├── clients/ # Moduł klientów
├── expenses/ # Moduł wydatków
├── invoices/ # Moduł faktur
├── receipts/ # Moduł paragonów
├── store/ # NGXS state management
└── ... # Pozostałe moduły
Wzorce architektoniczne¶
1. Feature-based organization¶
Każda funkcjonalność biznesowa (budżety, klienci, wydatki) jest osobną biblioteką Nx:
// Import z innej biblioteki
import { BudgetService } from '@msk/budgets';
import { ClientService } from '@msk/clients';
2. Layered architecture w bibliotekach¶
Każda biblioteka ma warstwową strukturę:
projects/budgets/
├── components/ # Komponenty UI
├── services/ # Logika biznesowa
├── state/ # NGXS state
├── models/ # TypeScript interfaces
├── constants/ # Stałe
└── utils/ # Funkcje pomocnicze
3. State management (NGXS)¶
Centralizacja stanu aplikacji z NGXS:
// State class
@State<BudgetStateModel>({
name: 'budgets',
defaults: { list: [], selected: null }
})
export class BudgetState {
@Action(LoadBudgets)
loadBudgets(ctx: StateContext<BudgetStateModel>) {
// ...
}
}
// Komponent
export class BudgetListComponent {
budgets$ = this.store.select(BudgetState.getBudgets);
constructor(private store: Store) {}
}
4. Abstract base classes¶
Reużywalne klasy bazowe dla typowych wzorców:
// projects/common/src/lib/services/abstract-api.service.ts
export abstract class AbstractApiService<T> {
abstract getAll(): Observable<T[]>;
abstract getById(id: string): Observable<T>;
abstract create(item: T): Observable<T>;
abstract update(id: string, item: T): Observable<T>;
abstract delete(id: string): Observable<void>;
}
// Implementacja
export class BudgetService extends AbstractApiService<Budget> {
// Implementacja specificzna dla budżetów
}
5. Reactive programming (RxJS)¶
Wszystkie operacje async używają RxJS:
// Przykład: łączenie wielu strumieni
combineLatest([
this.budgetService.getAll(),
this.clientService.getAll(),
this.userService.getCurrentUser()
]).pipe(
map(([budgets, clients, user]) => ({
budgets: budgets.filter(b => b.userId === user.id),
clients
}))
).subscribe(result => {
// Przetwarzanie
});
Architektura backendowa (masaku-api)¶
Clean Architecture / Layered Architecture¶
API wykorzystuje warstwową architekturę:
masaku-api/
├── Masaku.API/ # Warstwa prezentacji
│ ├── Controllers/ # REST endpoints
│ ├── Middlewares/ # Request pipeline
│ └── Extensions/ # DI configuration
├── Masaku.Services/ # Warstwa logiki biznesowej
│ ├── Services/ # Business logic
│ ├── Validators/ # Validation
│ └── Mappers/ # DTO mapping
├── Masaku.Repository/ # Warstwa dostępu do danych
│ ├── Repositories/ # Data access
│ ├── Context/ # EF DbContext
│ └── Migrations/ # Liquibase
└── Masaku.Domain/ # Warstwa domenowa
├── Entities/ # Domain models
└── Interfaces/ # Abstrakcje
Zależności między warstwami¶
┌────────────────────────────────────────┐
│ Masaku.API │
│ (Controllers, Middleware) │
└────────────────┬───────────────────────┘
│ depends on
▼
┌────────────────────────────────────────┐
│ Masaku.Services │
│ (Business Logic) │
└────────────────┬───────────────────────┘
│ depends on
▼
┌────────────────────────────────────────┐
│ Masaku.Repository │
│ (Data Access) │
└────────────────┬───────────────────────┘
│ depends on
▼
┌────────────────────────────────────────┐
│ Masaku.Domain │
│ (Entities, Interfaces) │
└────────────────────────────────────────┘
Dependency Injection¶
ASP.NET Core DI container zarządza wszystkimi zależnościami:
// Masaku.API/Extensions/ServiceCollectionExtensions.cs
public static IServiceCollection AddApplicationServices(
this IServiceCollection services)
{
// Repositories
services.AddScoped<IBudgetRepository, BudgetRepository>();
// Services
services.AddScoped<IBudgetService, BudgetService>();
// Validators
services.AddScoped<IValidator<Budget>, BudgetValidator>();
return services;
}
Repository Pattern¶
Abstrakcja dostępu do danych:
// Masaku.Domain/Interfaces/IBudgetRepository.cs
public interface IBudgetRepository
{
Task<IEnumerable<Budget>> GetAllAsync();
Task<Budget> GetByIdAsync(int id);
Task<Budget> CreateAsync(Budget budget);
Task UpdateAsync(Budget budget);
Task DeleteAsync(int id);
}
// Masaku.Repository/Repositories/BudgetRepository.cs
public class BudgetRepository : IBudgetRepository
{
private readonly MasakuDbContext _context;
public BudgetRepository(MasakuDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Budget>> GetAllAsync()
{
return await _context.Budgets.ToListAsync();
}
// ...
}
Komunikacja Frontend-Backend¶
REST API¶
Frontend komunikuje się z backendem przez RESTful API:
// Frontend: projects/budgets/src/lib/services/budget-api.service.ts
@Injectable()
export class BudgetApiService extends AbstractApiService<Budget> {
private apiUrl = '/api/budgets';
getAll(): Observable<Budget[]> {
return this.http.get<Budget[]>(this.apiUrl);
}
getById(id: string): Observable<Budget> {
return this.http.get<Budget>(`${this.apiUrl}/${id}`);
}
}
// Backend: Masaku.API/Controllers/BudgetsController.cs
[ApiController]
[Route("api/[controller]")]
public class BudgetsController : ControllerBase
{
private readonly IBudgetService _budgetService;
[HttpGet]
public async Task<IActionResult> GetAll()
{
var budgets = await _budgetService.GetAllAsync();
return Ok(budgets);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var budget = await _budgetService.GetByIdAsync(id);
return Ok(budget);
}
}
Autentykacja i autoryzacja¶
Azure AD B2C¶
System używa Azure AD B2C do autentykacji:
// Frontend: MSAL configuration
export const msalConfig = {
auth: {
clientId: environment.azureClientId,
authority: environment.azureAuthority,
redirectUri: environment.redirectUri
}
};
// Backend: JWT validation
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["AzureAdB2C:Authority"];
options.Audience = configuration["AzureAdB2C:ClientId"];
});
Authorization Flow¶
1. User → Frontend: Login request
2. Frontend → Azure AD B2C: Redirect to login
3. Azure AD B2C → Frontend: Return JWT token
4. Frontend → Backend: API request with Bearer token
5. Backend: Validate JWT, process request
6. Backend → Frontend: Response
Multi-region support¶
System obsługuje dwa regiony (DE i AT) z osobnymi:
Frontend¶
// src/environments/de/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.masaku.de',
region: 'DE'
};
// src/environments/at/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.masaku.at',
region: 'AT'
};
Backend¶
// appsettings.Germany.json
{
"Region": "DE",
"ConnectionStrings": {
"DefaultConnection": "..."
}
}
// appsettings.Austria.json
{
"Region": "AT",
"ConnectionStrings": {
"DefaultConnection": "..."
}
}
Bezpieczeństwo¶
Frontend¶
- JWT token storage: HttpOnly cookies (planned) lub sessionStorage
- XSS protection: Angular built-in sanitization
- CSRF protection: CSRF tokens w formularzach
- CSP headers: Content Security Policy
Backend¶
- Authentication: Azure AD B2C JWT validation
- Authorization: Role-based access control
- Input validation: FluentValidation
- SQL injection protection: Entity Framework parameterized queries
- CORS: Skonfigurowane allowed origins
Skalowanie i performance¶
Frontend¶
- Lazy loading: Moduły ładowane on-demand
- Bundle splitting: Separate chunks per route
- AOT compilation: Ahead-of-time compilation
- Tree shaking: Usuwanie nieużywanego kodu
- CDN: Static assets z CDN
Backend¶
- Caching: Redis (planned) lub in-memory
- Connection pooling: EF Core connection pool
- Async/await: Wszystkie I/O operations async
- Load balancing: Azure App Service (multiple instances)
Monitoring i logging¶
Frontend¶
- Error tracking: Sentry (planned) lub Application Insights
- Analytics: Google Analytics / Azure Application Insights
- Console logging: Environment-based log levels
Backend¶
- Application Insights: Telemetria i metryki
- Structured logging: Serilog
- Health checks: ASP.NET Core health check endpoints
- Exception handling: Global exception middleware
Diagram przepływu danych¶
User Action
│
▼
┌─────────────────┐
│ Component │
└────────┬────────┘
│ dispatch action
▼
┌─────────────────┐
│ NGXS Action │
└────────┬────────┘
│ calls
▼
┌─────────────────┐
│ API Service │
└────────┬────────┘
│ HTTP request
▼
┌─────────────────┐
│ Controller │ (Backend)
└────────┬────────┘
│ calls
▼
┌─────────────────┐
│ Business Svc │
└────────┬────────┘
│ calls
▼
┌─────────────────┐
│ Repository │
└────────┬────────┘
│ query
▼
┌─────────────────┐
│ Database │
└─────────────────┘