Code-Konventionen¶
TCM365 erzwingt konsistente Codierungsstandards in der gesamten Codebasis. Dieser Guide behandelt TypeScript/Backend-Konventionen, Vue/Frontend-Konventionen, Datenbank-Patterns und das verpflichtende Commit-Nachrichtenformat.
TypeScript / Backend Konventionen¶
Code Style¶
TCM365 verwendet ESLint und Prettier mit folgender Konfiguration:
| Einstellung | Wert |
|---|---|
singleQuote |
true |
trailingComma |
all |
printWidth |
100 |
tabWidth |
2 |
semi |
true |
Formatierung und Linting ausführen:
TypeScript Strict Mode¶
Das Backend verwendet striktes TypeScript mit folgenden Compiler-Optionen:
noImplicitAny-- Keine implizitenanyTypenstrictNullChecks-- Null Safety Enforcementstrict-- Alle strikten Checks aktiviert
Keine any Typen
Die Verwendung von any ist in der gesamten Codebasis verboten. Verwenden Sie stattdessen spezifische Typen, Generics oder unknown mit Type Guards.
Namenskonventionen¶
| Element | Konvention | Beispiel |
|---|---|---|
| Entities | PascalCase | M365Tenant, VendorTenant |
| Services | PascalCase | SnapshotService, GraphService |
| Controllers | PascalCase | SnapshotsController |
| Interfaces | PascalCase | VendorAdapter |
| DTOs | PascalCase | CreateSnapshotDto |
| Methoden | camelCase | createSnapshot(), findById() |
| Variablen | camelCase | tenantConfig, snapshotData |
| Konstanten | UPPER_SNAKE | UTCM_LIMITS, ALL_GRAPH_ENDPOINTS |
| DB Spalten | snake_case | created_at, vendor_tenant_id |
| DB Tabellen | snake_case | vendor_tenants, audit_logs |
| Routen | kebab-case | /configuration-drift, /ca-whatif |
| Dateien | kebab-case | vendor-adapter.interface.ts |
NestJS Modul-Pattern¶
Jedes Feature-Modul folgt dem Standard NestJS Modul-Pattern:
modules/{feature}/
├── {feature}.module.ts # Modul-Definition
├── {feature}.controller.ts # HTTP Endpoints
├── {feature}.service.ts # Business-Logik
├── {feature}.service.spec.ts # Unit Tests
└── dto/ # Data Transfer Objects
├── create-{feature}.dto.ts
└── update-{feature}.dto.ts
Modul-Definition:
@Module({
imports: [TypeOrmModule.forFeature([MyEntity])],
controllers: [MyController],
providers: [MyService],
exports: [MyService],
})
export class MyModule {}
Service Patterns¶
- Dependency Injection verwenden -- Services niemals manuell instanziieren
- Immer async/await -- Alle I/O Operationen müssen asynchron sein
- Config via ConfigService -- Niemals
process.envdirekt verwenden - TypeORM Repositories verwenden -- Keine rohen SQL Queries schreiben
@Injectable()
export class MyService {
constructor(
@InjectRepository(MyEntity)
private readonly myRepo: Repository<MyEntity>,
private readonly configService: ConfigService,
) {}
async findAll(): Promise<MyEntity[]> {
return this.myRepo.find();
}
}
DTO Validation¶
Alle DTOs verwenden class-validator Decorators für Input-Validierung:
import { IsString, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateExampleDto {
@ApiProperty({ description: 'Name des Beispiels' })
@IsString()
@IsNotEmpty()
name: string;
@ApiPropertyOptional({ description: 'Optionale Beschreibung' })
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: 'Vendor Tenant ID' })
@IsUUID()
vendorTenantId: string;
}
Path Aliases¶
Verwenden Sie Path Aliases anstelle von tiefen relativen Imports:
// Bevorzugt:
import { User } from '@entities/user.entity';
import { JwtAuthGuard } from '@common/guards/jwt-auth.guard';
// Vermeiden:
import { User } from '../../../entities/user.entity';
Verfügbare Aliases: @/*, @config/*, @database/*, @entities/*, @modules/*, @common/*.
Guard und Decorator Verwendung¶
@Controller('my-feature')
@ApiTags('My Feature')
@UseGuards(JwtAuthGuard, RolesGuard)
export class MyController {
@Get()
@Roles(UserRole.VIEWER)
@ApiOperation({ summary: 'Alle Einträge auflisten' })
async findAll(@CurrentUser() user: User) {
// ...
}
@Post()
@Roles(UserRole.TENANT_ADMIN)
@Permissions('my-feature:write')
@ApiOperation({ summary: 'Neuen Eintrag erstellen' })
async create(@Body() dto: CreateDto, @CurrentUser() user: User) {
// ...
}
}
Vue / Frontend Konventionen¶
Komponenten-Benennung¶
| Element | Konvention | Beispiel |
|---|---|---|
| Komponenten | PascalCase | TenantCard.vue, DiffViewer.vue |
| Views | PascalCase | SnapshotsView.vue |
| Nicht-Komponenten | kebab-case | snapshot-helpers.ts |
| Composables | camelCase | useAsyncAction.ts |
| Services | camelCase | snapshotService.ts |
Composition API¶
TCM365 verwendet die Vue 3 Composition API mit <script setup>:
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { snapshotService } from '@/services/snapshotService';
const authStore = useAuthStore();
const snapshots = ref([]);
const loading = ref(false);
const filteredSnapshots = computed(() =>
snapshots.value.filter(s => s.status === 'completed')
);
onMounted(async () => {
loading.value = true;
try {
snapshots.value = await snapshotService.getAll();
} finally {
loading.value = false;
}
});
</script>
Tailwind CSS¶
Alle Styles verwenden Tailwind CSS Utility-Klassen. Vermeiden Sie eigene CSS-Dateien, es sei denn es ist zwingend erforderlich.
<template>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
{{ title }}
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400">
{{ description }}
</p>
</div>
</template>
State Management¶
Verwenden Sie Pinia Stores in frontend/src/stores/ für geteilten State. Halten Sie komponentenlokalen State in ref() und reactive().
API Aufrufe¶
Alle API Aufrufe erfolgen über Service-Dateien in frontend/src/services/:
// services/snapshotService.ts
import api from './api';
export const snapshotService = {
getAll: () => api.get('/snapshots'),
getById: (id: string) => api.get(`/snapshots/${id}`),
create: (data: any) => api.post('/snapshots', data),
bulkDelete: (ids: string[]) => api.post('/snapshots/bulk-delete', { ids }),
};
Datenbank-Konventionen¶
Primary Keys¶
Alle Entities verwenden UUID Primary Keys (keine Auto-Increment Integer):
@Entity('examples')
export class Example extends BaseEntity {
// id (UUID), created_at, updated_at geerbt von BaseEntity
}
Timestamps¶
Die meisten Tabellen enthalten created_at und updated_at Spalten, geerbt von BaseEntity.
JSONB Spalten¶
Verwenden Sie PostgreSQL jsonb für flexible Metadaten-Speicherung:
Migrationen¶
- Alle Schema-Änderungen erfordern TypeORM Migrationen in
backend-js/src/database/migrations/ - Migrationen müssen deterministisch und reversibel sein
- Tenant-bezogene Tabellen müssen sowohl im
publicals auch in bestehendentenant_{uuid}Schemas erstellt werden - Bestehende Migrationsdateien niemals ändern -- immer neue Migrationen erstellen
Row-Level Security¶
Die Datenbank verwendet duale PostgreSQL Rollen:
| Rolle | Zweck |
|---|---|
tcm_admin |
Migrations-Rolle mit BYPASSRLS |
tcm_app |
Laufzeit-Rolle, die RLS Policies respektiert |
Die Applikation setzt app.current_org_id auf jeder Datenbankverbindung für automatische Zeilenfilterung.
Single Table Inheritance (STI)¶
Vendor Tenants verwenden STI mit einer vendor Discriminator-Spalte:
// Basis-Entity
@Entity('vendor_tenants')
@TableInheritance({ column: { type: 'varchar', name: 'vendor' } })
export class VendorTenant extends BaseEntity { ... }
// Kind-Entity
@ChildEntity('microsoft')
export class M365VendorTenant extends VendorTenant { ... }
// Kind-Entity
@ChildEntity('zscaler')
export class ZscalerVendorTenant extends VendorTenant { ... }
Neue Vendors
Neue Vendor-Integrationen erstellen einen neuen @ChildEntity der VendorTenant erweitert -- keine separate Tabelle. Dies stellt konsistente Abfragen und Beziehungen über alle Vendors hinweg sicher.
Conventional Commits (Pflicht)¶
Alle Commits müssen der Conventional Commits Spezifikation folgen.
Format¶
Commit Types¶
| Type | SemVer Auswirkung | Wann verwenden |
|---|---|---|
feat |
MINOR Bump | Neues Feature oder Funktionalität |
fix |
PATCH Bump | Bugfix |
feat! |
MAJOR Bump | Breaking Change (haenge ! nach Typ an) |
fix! |
MAJOR Bump | Breaking Bugfix |
docs |
keine | Nur Dokumentationsaenderungen |
test |
keine | Tests hinzufügen oder aktualisieren |
refactor |
keine | Code-Änderung ohne Verhaltensänderung |
perf |
keine | Performance-Verbesserung |
chore |
keine | Dependencies, Config, Tooling |
Scopes (optional, aber empfohlen)¶
| Scope-Format | Beispiel |
|---|---|
| Modulnamen | fix(snapshots): correct bulk delete query |
| Bereichsnamen | chore(deps): update NestJS to 10.3 |
| Jira Referenzen | fix(anomaly): DTO validation TCM-14 |
Beispiele¶
# Feature
feat(snapshots): add bulk delete endpoint
# Bugfix
fix(drift): correct drift severity calculation for Zscaler
# Breaking Change
feat!: redesign tenant API response format
BREAKING CHANGE: Tenant API responses now use camelCase instead of snake_case.
# Dokumentation
docs(api): update endpoint reference for v2.5.0
# Test
test(baselines): add compliance engine unit tests
# Chore
chore(deps): upgrade TypeORM to 0.3.20
Code Review Checkliste¶
Vor der Code-Abgabe zur Review sicherstellen:
- Code folgt Namenskonventionen (PascalCase Entities, camelCase Methoden, snake_case DB)
- DTOs haben
class-validatorDecorators - Controller haben Swagger Decorators (
@ApiOperation,@ApiResponse,@ApiTags) - Guards sind korrekt angewendet (
@UseGuards,@Roles,@Permissions) - Alle I/O Operationen verwenden
async/await - Config wird via
ConfigServiceabgerufen, nichtprocess.env - Tests geschrieben und bestanden
- Code formatiert mit
npm run format - Keine Lint-Fehler von
npm run lint - Commit-Nachricht folgt Conventional Commits
- Neue Entities haben zugehörige Migrationen
- Tenant-bezogene Entities aktualisieren sowohl Public als auch Tenant Schemas