AgentSkillsCN

backend-architecture

Electron Main Process 后端开发指南。采用 Repository-Service-IPC 分层架构,涵盖模块化开发、数据库操作、错误处理以及安全防护等最佳实践。在编写后端代码、新增模块,或进行数据库相关操作时,均可参考此指南。

SKILL.md
--- frontmatter
name: backend-architecture
description: Electron Main Process backend geliştirme rehberi. Repository-Service-IPC katmanlı mimari, modül oluşturma, veritabanı işlemleri, hata yönetimi ve güvenlik kalıpları. Backend kodu yazarken, yeni modül eklerken veya veritabanı işlemleri yaparken kullanılır.

Backend Architecture (Electron Main Process)

Katmanlı mimari: Repository → Service → IPC → Frontend. Her katmanın tek sorumluluğu vardır.

Mimari Genel Bakış

code
ServiceManager (IPC kayıt)
  └── BaseService<T> (iş mantığı + IPC handler)
        └── BaseRepository<T> (veri erişim + CRUD)
              └── Database (singleton SQLite bağlantısı)

Yardımcılar: AppError (hata), Logger (loglama)

Yeni Modül Ekleme İş Akışı

Yeni bir backend modülü eklerken bu sırayla ilerleyin:

Adım 1: Shared Types (önce tipler)

src/shared/types/{modul}.types.ts dosyasını oluşturup index.ts'e ekleyin. Detaylar için shared-contracts skill kullanın.

Adım 2: Repository Oluştur

typescript
// src/main/modules/{modul-adi}/{modul-adi}.repository.ts
import { BaseRepository } from '@main/core/BaseRepository'
import type { YourEntity } from '@shared/types'

export class YourRepository extends BaseRepository<YourEntity> {
  protected getTableName(): string {
    return 'your_table'
  }

  protected override getBooleanColumns(): readonly string[] {
    return ['is_active']  // SQLite 0/1 <-> boolean dönüşümü
  }

  protected getTableSchemas(): readonly string[] {
    return [
      `CREATE TABLE IF NOT EXISTS your_table (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        is_active INTEGER NOT NULL DEFAULT 1 CHECK(is_active IN (0, 1)),
        created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
        updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
      )`
    ]
  }

  // Özel sorgular — her zaman safeExecute içinde
  findByTitle(title: string): YourEntity | null {
    return this.findOneBy('title', title)
  }
}

Adım 3: Service Oluştur

typescript
// src/main/modules/{modul-adi}/{modul-adi}.service.ts
import { BaseService } from '@main/core/BaseService'
import { AppError } from '@main/core/AppError'
import { YourRepository } from './{modul-adi}.repository'
import type { ServiceResponse, YourEntity, CreateYourRequest } from '@shared/types'
import type { ServiceHandlerMap } from '@main/core/types'

export class YourService extends BaseService<YourEntity> {
  protected repository: YourRepository

  constructor() {
    super()
    this.repository = new YourRepository()
  }

  getModuleName(): string {
    return 'YourService'
  }

  getChannelPrefix(): string {
    return 'your-module'  // IPC kanalı: your-module:get-all, your-module:create, vb.
  }

  // Standart CRUD'u override et (validasyon ekle)
  protected override async handleCreate(data: unknown): Promise<ServiceResponse<unknown>> {
    const input = data as CreateYourRequest
    if (!input.title?.trim()) throw AppError.badRequest('Başlık zorunludur')
    const item = this.repository.create({ title: input.title.trim(), is_active: true })
    return this.created(item, 'Kayıt oluşturuldu')
  }

  // Özel IPC kanalları
  protected override getCustomHandlers(): ServiceHandlerMap {
    return {
      'your-module:search': (data) => this.search(data as { query: string }),
    }
  }

  private async search(data: { query: string }): Promise<ServiceResponse<YourEntity[]>> {
    const results = this.repository.findByTitle(data.query)
    return this.ok(results ? [results] : [], 'Arama tamamlandı')
  }
}

Adım 4: ServiceManager'a Kaydet

typescript
// src/main/index.ts — initializeServices() içine ekle
serviceManager.register(new YourService())

Adım 5: Frontend Entegrasyonu

src/renderer/src/lib/api.ts dosyasına API fonksiyonları ekleyin. Detaylar için frontend-architecture skill kullanın.


Temel Sınıflar Referansı

BaseRepository<T> — Miras Alınan Özellikler

MetodAçıklamaOverride?
getTableName()Tablo adıZorunlu
getTableSchemas()CREATE TABLE SQL'leriZorunlu
getBooleanColumns()Boolean kolon listesiGerekirse
findAll()Tüm kayıtlar (DESC)Nadiren
findById(id)ID ile tek kayıtNadiren
findBy(col, val)Kolona göre çoklu kayıtHayır
findOneBy(col, val)Kolona göre tek kayıtHayır
create(data)Yeni kayıt oluşturHayır
update(id, data)Kayıt güncelle (+updated_at)Hayır
delete(id)Kayıt silHayır
exists(id)Var mı kontrolüHayır
count()Toplam kayıt sayısıHayır
safeExecute(fn)Güvenli DB işlemiHayır
safeTransaction(fn)Atomik transactionHayır

BaseService<T> — Miras Alınan Özellikler

MetodAçıklamaOverride?
getModuleName()Modül adı (log için)Zorunlu
getChannelPrefix()IPC kanal önekiZorunlu
handleGetAll(){prefix}:get-all handlerGerekirse
handleGetById(data){prefix}:get-by-id handlerNadiren
handleCreate(data){prefix}:create handlerSıklıkla (validasyon)
handleUpdate(data){prefix}:update handlerSıklıkla (validasyon)
handleDelete(data){prefix}:delete handlerGerekirse
getCustomHandlers()Özel IPC kanalları ekleSıklıkla
ok(data, msg)200 yanıt oluşturHayır
created(data, msg)201 yanıt oluşturHayır
fail(msg, code)Hata yanıtı oluşturHayır
requireId(data)ID doğrulamaHayır

Hata Yönetimi Kalıpları

typescript
// ✅ Her zaman AppError kullan
throw AppError.badRequest('Geçersiz veri')        // 400
throw AppError.unauthorized('Giriş yapılmadı')    // 401
throw AppError.forbidden('Yetkiniz yok')           // 403
throw AppError.notFound('Kayıt bulunamadı')        // 404
throw AppError.conflict('Zaten mevcut')            // 409
throw AppError.internal('Sunucu hatası')           // 500
throw AppError.busy('DB meşgul')                   // 503

// ❌ YASAK
throw new Error('...')
console.error('...')

DB Hata Çevirisi (Otomatik)

BaseRepository safeExecute içinde SQLite hatalarını otomatik çevirir:

SQLite HatasıAppErrorKod
UNIQUE constraintconflict()409
FOREIGN KEY constraintbadRequest()400
NOT NULL constraintbadRequest()400
CHECK constraintbadRequest()400
SQLITE_BUSY / lockedbusy()503
Diğerinternal()500

Güvenlik Kontrol Listesi

  • Şifreler bcryptjs ile hashlenmiş (SALT_ROUNDS = 10)
  • Yanıtlarda şifre alanı stripPassword() ile çıkarılmış
  • Rol hiyerarşisi kontrol edilmiş (superadmin > admin > user)
  • State-changing işlemlerde audit log yazılmış
  • SQL sorgularında parameterized query (?) kullanılmış
  • Boolean alanlar getBooleanColumns() ile tanımlı

Loglama

typescript
// Her zaman Logger singleton kullan
this.logger.info('Mesaj', 'KontekstAdı')
this.logger.warn('Uyarı', 'KontekstAdı')
this.logger.error('Hata', errorObj, 'KontekstAdı')
this.logger.debug('Debug bilgisi', 'KontekstAdı')

Dosya Başlık Şablonu

typescript
// ============================================================
// DosyaAdı - Kısa açıklama (Türkçe)
//
// Sorumlulukları:
// 1. ...
// 2. ...
// ============================================================

Detaylı Referanslar