# archlint: mimari sınırları CI'da deterministik olarak zorlamak

> Mimari kararlar wiki'de doğru kalır, kodda çürür. archlint, architecture.json'daki sınırları her commit'te go/parser ile, modelsiz zorluyor.

- Yayın: 2026-06-18
- Kategori: Günlük
- Etiketler: Go, GitHub Actions, Git
- Okuma süresi: 2 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/archlint-mimari-sinirlari-ci-da-zorlamak/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
[Mimari kararların neden çürüdüğünü](https://sade.dev/tr/journal/mimari-kararlar-curur) ayrı yazdım: bir ADR "domain altyapıyı import etmesin" der, iki yıl sonra bir bakarsın yüzlerce yerde import etmiş — çünkü kuralı izleyen hiçbir şey yoktu. Bu yazı o sorunun araç tarafı: küçük, bağımsız bir Go CLI — **`archlint`**.

## architecture.json: kuralı koda yaz

Katmanları ve aralarındaki izinli bağımlılıkları tek bir dosyada beyan ediyorsun:

```json
{
  "module": "github.com/acme/app",
  "layers": {
    "domain": ["internal/domain"],
    "db":     ["internal/db"],
    "http":   ["internal/http"]
  },
  "rules": {
    "domain": [],
    "db":     ["domain"],
    "http":   ["domain", "db"]
  }
}
```

`domain` içeride hiçbir şey import edemez, `db` yalnızca `domain`'i, `http` ikisini de. Başka her iç kenar bir ihlal. Aynı-katman import'u her zaman serbest; `[]` "hiçbir katmanı import edemez" demek.

## Nasıl çalışıyor

`archlint check`, her `.go` dosyasının import'larını **stdlib'in `go/parser`'ıyla** çıkarıyor — regex tahmini değil, derlemeye gerek yok, kesin. Her dosyayı ve her import'u (modül yolunu sıyırıp) bir katmana çözüyor; kuralı çiğneyen kenarı dosya:satır ile raporluyor:

```text
$ archlint check examples/sample
Scanned examples/sample against examples/sample/architecture.json — 2 layer(s).

1 boundary violation(s):
  internal/domain/bad.go:6  domain → db is not allowed  (import "github.com/acme/app/internal/db")
```

İhlal varsa exit 1 → CI kırmızı. Çiğneyen import `main`'e ulaşmadan yakalanıyor; iki yıl sonra bir arkeoloji seansında değil.

## Tasarım kararları

- **Deterministik, modelsiz.** Bütün amaç CI'da kapı kurabileceğin bir guardrail: aynı diff, aynı verdict, döngüde model yok. Bir LLM'e "mimariyi gözden geçir" demekten farkı bu.
- **Sıfır bağımlılık** (Go stdlib). Config şimdilik JSON — YAML bir bağımlılık ekliyor, o yüzden follow-up.
- **Önce Go.** `go/parser` Go import'larını kusursuz veriyor; keskin bir MVP tek dilde iyi yapılır. TypeScript ve Python, kendi parser'larıyla sıradaki adım.

## CI'da

Buradan sonrası, [GitHub Actions ile kurduğum akışların](/blog/biri-github-actions-mi-dedi/) alışılmış iki satırı; `archlint`'in tek beklentisi, ihlalde sıfırdan farklı bir kod dönerek job'ı düşürebilmek.

```yaml
- run: go install github.com/muhammetsafak/archlint/cmd/archlint@latest
- run: archlint check
```

Ya da paketlenmiş [GitHub Action](https://github.com/muhammetsafak/archlint) ile:

<!--email_off-->

```yaml
- uses: muhammetsafak/archlint@v0.3.0
```

<!--/email_off-->

## Sınırlar — dürüst liste

- **Go, şimdilik.** Import'lar `go/parser` ile okunuyor (kesin); TS/Python kendi parser'larıyla sıradaki adım.
- **Import sınırları, şimdilik.** Katmanlar arası bağımlılık grafiğini yönetir. "Senkron olması gereken yerde async çağrı" ya da domain sınırını aşan doğrudan bir DB sorgusu, runtime trace'leri ilişkilendirmeyi gerektirir — sonraki faz.
- **JSON config** (YAML follow-up). Deterministik tasarım kasıtlı: gate'lenebilir olsun diye.

## Denemek için

- [github.com/muhammetsafak/archlint](https://github.com/muhammetsafak/archlint)

`architecture.json`'unu yaz, `archlint check` çalıştır — ya da `archlint check examples/sample` ile kasıtlı bir ihlalin yakalandığını gör. Hangi sınırı bir sonraki sürümde (TS? Python?) görmek istediğini yazarsan, sıraya koyarım.
