Eklenti(Plugin) Sistemi
Bir bilgisayar sistemi, transistörden uygulama eklentisine kadar tek bir bütün olarak tasarlanmaz. Her katman kendi altındaki katmanın ayrıntılarını gizler ve üstündeki katmana sınırlı, tanımlı bir arayüz sunar. İşletim sistemi işlemcinin elektriksel davranışını, uygulama disk denetleyicisinin komut setini, eklenti de uygulamanın iç veri yapılarını bilmek zorunda kalmaz.
Eklenti sistemi bu fikrin uygulama düzeyindeki karşılığıdır. Ana uygulama kararlı bir sözleşme tanımlar; eklentiler bu sözleşmeyi uygular; bir kayıt mekanizması da uygun eklentileri bulur, doğrular ve sisteme ekler. Bir aygıt sürücüsünün kernel'e kayıt olması ile bir eklentinin uygulamaya kayıt olması aynı tasarım problemidir: kararlı arayüz, sürüm uyumluluğu, yaşam döngüsü ve hata yalıtımı.
Bu yaklaşımın değeri yalnızca kodu ayrı dizinlere bölmekten gelmez. İyi tasarlanmış bir eklenti sistemi aşağıdaki soruları açıkça yanıtlar:
- Eklenti hangi arayüzü uygulamalıdır?
- Eklentiler nasıl keşfedilir ve sisteme bağlanır?
- Uyumsuz veya hatalı bir eklenti nasıl reddedilir?
- Eklentinin başlatılması ve kapatılması nasıl yönetilir?
- Bir eklenti hatasının ana uygulamayı etkilemesi nasıl sınırlandırılır?
- Sözleşme değiştiğinde geriye dönük uyumluluk nasıl korunur?
Yazının uygulama bölümü, Go ile yazılmış log-analyzer projesindeki eklenti sistemini örnek alır. Proje; log analizi, ağ telemetrisi ve akış işleme yeteneklerini çekirdeğe gömmek yerine birbirinden bağımsız üç eklenti olarak sunar.
Katmanlı Mimaride Eklentinin Yeri
Donanımdan uygulama eklentilerine uzanan yedi katman, eklenti sisteminin bütün içindeki yerini göstermek için kısa bir kavramsal model olarak kullanılabilir.
| Katman | Temel Sorumluluk | Sözleşme |
|---|---|---|
| 1. Donanım | İşlem, bellek, depolama ve giriş çıkış işlevlerini fiziksel olarak gerçekleştirir. | Komut seti mimarisi |
| 2. Kernel | Süreçleri, belleği, dosya sistemlerini ve aygıt erişimini yönetir. | Sistem çağrıları |
| 3. Donanım soyutlama | Platforma özgü donanım ayrıntılarını ortak arayüzlerin arkasında toplar. | Sürücü ve HAL arayüzleri |
| 4. Kullanıcı alanı kütüphaneleri | Uygulamaların tekrar kullandığı ortak işlevleri sağlar. | Kütüphane API'si ve sembolleri |
| 5. API ve ABI | Bileşenlerin kaynak kodu veya ikili düzeyde nasıl haberleşeceğini tanımlar. | Sürümlenmiş arayüz tanımı |
| 6. Uygulama | İş kurallarını ve kullanıcıya sunulan temel davranışları yürütür. | Kullanıcı ve servis arayüzleri |
| 7. Eklenti | Uygulamanın extension API'sine uyarak yeni davranış ekler. | Extension API |
Bilgi
Bu yedi katman standartlaştırılmış bir referans modeli değildir. Eklenti katmanının donanım ve işletim sistemi soyutlamalarıyla ortak tasarım fikrini göstermek için kullanılan açıklayıcı bir sınıflandırmadır.

Donanım ve Kernel
En alt katmanda işlemci, bellek, depolama ve giriş çıkış denetleyicileri bulunur. Bu katmanın yazılıma verdiği söz komut seti mimarisidir. Komut seti aynı kaldığı sürece üretici iç tasarımı değiştirebilir; farklı önbellek düzenine sahip iki işlemci aynı ikili dosyayı çalıştırır.

Kernel donanıma doğrudan erişimi tekeline alır ve bu erişimi sistem çağrıları üzerinden dağıtır. Burada iki ayrı sözleşme vardır ve kararlılık dereceleri farklıdır: kullanıcı alanına bakan sistem çağrısı arayüzü uzun süreli uyumluluk vaat eder, kernel'in iç arayüzleri ise kararlı değildir. Bu ayrım eklenti tasarımı için doğrudan bir derstir. Dışarıya açılan sözleşme ile iç yapı aynı şey değildir ve ikisi aynı hızda değişmemelidir.

Donanım Soyutlama ve Kütüphaneler
Donanım soyutlama katmanı platforma özgü ayrıntıları ortak arayüzlerin arkasında toplar. Windows'ta HAL kesme denetleyicisi, zamanlayıcı ve veri yolu farklılıklarını gizler; Linux tarafında benzer rolü aygıt sürücü modelleri üstlenir.

Aygıt sürücüsü bu katmanın eklentisidir. Kernel belirli bir sürücüyü tanımaz; sürücü tanımlı bir yapı üzerinden kendini kaydeder, bir yaşam döngüsü boyunca çalışır ve kaldırıldığında kaynaklarını bırakır. Bir üst katmanda ise uygulamaların tekrar yazmak istemediği işlevler kütüphanelerde toplanır; paylaşımlı kütüphanelerin çalışma anında yüklenmesi eklenti fikrinin en eski biçimidir.

API ve ABI
API, bileşenlerin kaynak kodu düzeyinde nasıl haberleşeceğini tanımlar: fonksiyon adları, parametreler, dönüş değerleri, hata davranışı. ABI ise aynı sorunun ikili düzeydeki karşılığıdır: çağrı kuralları, veri hizalaması, yapı düzeni, sembol adlandırma.

Aradaki fark pratikte şuna karşılık gelir. API kırılırsa kod derlenmez; hata erkendir ve görünürdür. ABI kırılırsa kod derlenir ama çalışma anında beklenmedik biçimde bozulur. Örnekteki sistemde eklentiler ana uygulamayla birlikte derlendiği için ikili uyumluluk sorunu doğmaz; uyumsuzluk derleme hatasına dönüşür. Eklentilerin ayrı dağıtıldığı sistemlerde ise sürümleme ve uyumluluk politikası yazılı olmak zorundadır.
Uygulama ve Eklenti
Uygulama katmanı iş kurallarını yürütür ve alt katmanların yeteneklerini belirli bir amaca göre düzenler. En üstte ise uygulamanın extension API'sine uyarak yeni davranış ekleyen bileşenler bulunur.

Buradaki bağımlılık yönü dikkat çekicidir. Eklenti ana uygulamayı bilir; ana uygulama eklentiyi bilmez. Bu asimetri, sistem yeni eklentilerle genişlerken çekirdeğin sabit kalmasını mümkün kılar.

Katmanlar Arasındaki Ortak Fikir
| Soru | Kernel modülü | Uygulama eklentisi |
|---|---|---|
| Arayüz nedir | Kernel modül API'si | Plugin arayüzü |
| Nasıl bulunur | Modül dizini ve yapılandırma | Dizin taraması ve kod üretimi |
| Nasıl kaydedilir | Kayıt fonksiyonları | plugin.Register() |
| Uyumluluk nasıl denetlenir | Sürüm ve sembol denetimi | Derleme anında arayüz uyumu |
| Yaşam döngüsü | Yükleme ve kaldırma | init() ve komut yürütme |
| Hata sınırı | Süreç ve ayrıcalık seviyeleri | Hata dönüşü, ayrı süreç veya servis |
Eklenti sistemi bu nedenle yeni bir fikir değil, işletim sistemi tasarımında uzun süredir kullanılan bir yaklaşımın uygulama düzeyine taşınmış halidir.

Eklenti Sistemi Nedir
Eklenti, ana uygulamadan bağımsız geliştirilebilen ancak ana uygulamanın tanımladığı sözleşme üzerinden çalışan bir yazılım bileşenidir. Eklenti yeni bir komut, veri dönüştürücü, doğrulama kuralı, çıktı biçimi veya entegrasyon sağlayabilir.
Kodu ayrı bir dizine koymak tek başına eklenti sistemi oluşturmaz. Tam bir eklenti sistemi en az şu bileşenleri içerir:
| Bileşen | Görev |
|---|---|
| Extension API | Eklentinin sağlaması gereken davranışları ve veri biçimlerini tanımlar. |
| Keşif mekanizması | Kullanılabilir eklentileri bulur. |
| Yükleyici veya bağlayıcı | Eklenti kodunu sisteme dahil eder ve nesnesini oluşturur. |
| Doğrulayıcı | Eklentinin sözleşmeye ve desteklenen sürüme uyduğunu denetler. |
| Registry | Kayıtlı eklentileri kimlikleriyle saklar. |
| Yaşam döngüsü yöneticisi | Başlatma, çalıştırma, durdurma ve kaynak temizleme işlemlerini koordine eder. |
| Hata ve güvenlik sınırı | Eklenti hatalarının ve yetkilerinin etkisini sınırlar. |
Çalışma Akışı
Bir eklentinin ana uygulamaya katılması genellikle aşağıdaki sırayı izler:
- Eklenti kaynakları taranır ve kullanılabilir eklentiler belirlenir.
- Bulunan eklenti kodu sisteme dahil edilir.
- Eklenti nesnesi oluşturulur ve registry'ye kaydedilir.
- Kimlik, sürüm ve gerekli metotlar doğrulanır.
- Ana uygulama kayıtlı eklentileri kendi akışına bağlar.
- Uygulama bir olay veya işlem isteğini ilgili eklentilere iletir.
- Hatalar eklenti sınırında kaydedilir ve politika gereği izole edilir.
- Uygulama kapanırken eklenti kaynakları serbest bırakılır.
Keşif, bağlama ve çalıştırma ayrı aşamalar olarak ele alınmalıdır. Bu ayrım, eklenti bulunamadığında oluşan hatayla eklenti çalışırken oluşan hatanın farklı biçimde yönetilmesini sağlar.
Extension API Tasarımı
Extension API, eklenti sisteminin en önemli parçasıdır. Sözleşme çok dar olursa eklentiler gerekli işlemleri yapamaz; çok geniş olursa uygulamanın iç ayrıntıları dışarı sızar ve API'nin değiştirilmesi zorlaşır.
İyi bir extension API şu özelliklere sahip olmalıdır:
- Eklentinin kullanabileceği veri ve işlemleri açıkça tanımlamalıdır.
- Uygulamanın iç nesnelerini doğrudan paylaşmak yerine sınırlı bir bağlam sunmalıdır.
- Girdi, çıktı ve hata davranışlarını belgelemelidir.
- Bir sürüm bilgisi içermelidir.
- Geriye dönük uyumluluk politikasını belirtmelidir.
- Eklenti kimliğinin benzersiz olmasını sağlamalıdır.
log-analyzer projesinde sözleşme üç metottan oluşur ve internal/plugin/plugin.go dosyasında tanımlanır.
package plugin
import "github.com/spf13/cobra"
type Plugin interface {
Name() string
Description() string
Command() *cobra.Command
}Sözleşmenin bu kadar dar olması bilinçli bir tercihtir. Ana uygulama eklentiden yalnızca üç şey ister: benzersiz bir ad, kullanıcıya gösterilecek bir açıklama ve CLI'ye eklenecek bir komut ağacı. Eklentinin logları nasıl ayrıştırdığı, hangi dosyaları okuduğu veya ağdan ne topladığı çekirdeği ilgilendirmez.
Go'da arayüz uyumu derleme anında denetlenir. Gerekli metotlardan birini sağlamayan bir tip Register() çağrısına geçemez; hata çalışma anında değil, derlemede ortaya çıkar.
Registry
Kayıtlı eklentiler internal/plugin/register.go içindeki registry'de tutulur.
package plugin
import (
"fmt"
"sort"
"sync"
)
var (
registry = make(map[string]Plugin)
mu sync.Mutex
)
func Register(p Plugin) {
mu.Lock()
defer mu.Unlock()
name := p.Name()
if name == "" {
panic("plugin: Register called with empty name")
}
if _, exists := registry[name]; exists {
panic(fmt.Sprintf("plugin: Register called twice for %q", name))
}
registry[name] = p
}
func All() []Plugin {
mu.Lock()
defer mu.Unlock()
names := make([]string, 0, len(registry))
for name := range registry {
names = append(names, name)
}
sort.Strings(names)
plugins := make([]Plugin, 0, len(names))
for _, name := range names {
plugins = append(plugins, registry[name])
}
return plugins
}
func Get(name string) (Plugin, bool) {
mu.Lock()
defer mu.Unlock()
p, ok := registry[name]
return p, ok
}Üç ayrıntı tasarım açısından önemlidir.
Birincisi doğrulama. Boş ad ve tekrarlanan ad kabul edilmez. İki eklentinin aynı adı kullanması, komut çakışması ve sessizce yanlış eklentinin çalışması anlamına gelir; bu nedenle hata gizlenmez.
İkincisi hatanın zamanı. Register() çağrısı panic ile sonuçlanır. Bu, çalışma anında kullanıcı girdisiyle tetiklenebilecek bir hata olsaydı yanlış bir tercih olurdu. Burada kayıt kümesi derleme anında bellidir ve kayıt program başlarken yapılır; dolayısıyla hata, binary'nin ilk çalıştırıldığı anda ve kesin biçimde ortaya çıkar. Eklentilerin kullanıcı tarafından çalışma anında yüklendiği bir sistemde ise aynı durum hata değeri döndürmeyi gerektirir.
Üçüncüsü sıralama. All(), eklentileri ada göre sıralayarak döndürür. Kayıt sırası init() çalışma sırasına bağlıdır ve bu sıraya güvenilmemelidir. Sıralama sayesinde list komutunun çıktısı ve komutların eklenme sırası her derlemede aynıdır.
Mutex kullanımı da registry'yi ileride eşzamanlı okuma yapan bileşenlere karşı korur.
Kendini Kaydeden Eklenti
Her eklenti kendi paketinde sözleşmeyi uygular ve init() içinde kendini kaydeder. Aşağıda log analizi eklentisinin tanımı yer alır.
package loganalyzer
import (
"log-analyzer/internal/plugin"
"github.com/spf13/cobra"
)
type LogAnalyzerPlugin struct{}
func init() {
plugin.Register(&LogAnalyzerPlugin{})
}
func (p *LogAnalyzerPlugin) Name() string {
return "loganalyzer"
}
func (p *LogAnalyzerPlugin) Description() string {
return "Analyzes system logs and detects security threats"
}
func (p *LogAnalyzerPlugin) Command() *cobra.Command {
return newRootCommand()
}Command() metodu eklentinin tüm komut ağacını döndürür. Eklentinin iç yapısı bu noktadan sonra kendi sorumluluğundadır.
func newRootCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "loganalyzer",
Short: "Log analysis and threat detection tool",
}
cmd.AddCommand(newStaticCommand())
cmd.AddCommand(newLiveCommand())
cmd.AddCommand(newInteractiveCommand())
return cmd
}Projedeki üç eklenti aynı sözleşmeyi uygular ancak tamamen farklı işler yapar:
| Eklenti | Açıklama | Alt komutlar |
|---|---|---|
loganalyzer | Sistem loglarını analiz eder ve güvenlik tehditlerini tespit eder. | static, live, interactive |
netmetrics | Scapy ile paket yakalar ve 5G telemetrisini UDP üzerinden iletir. | capture, gen5g, collect |
streamprocessor | Sınırlı bellekle gerçek zamanlı akış ayrıştırma ve filtreleme yapar. | run, gen |
Çekirdek kodda bu adların hiçbiri geçmez.
Keşif ve Kod Üretimi
Go'da bir paketin init() fonksiyonunun çalışması için o paketin binary'ye dahil edilmesi gerekir. Hiçbir yerden referans verilmeyen paket derlemeye girmez, dolayısıyla kendini kaydedemez. Projede bu sorun, eklenti dizinlerini tarayan ve boş tanımlayıcılı (blank) import listesi üreten bir araçla çözülür.
Proje Yapısı
log-analyzer/
├── cmd/
│ └── main.go
├── internal/
│ ├── cli/
│ │ └── root.go
│ └── plugin/
│ ├── plugin.go
│ └── register.go
├── plugins/
│ ├── doc.go
│ ├── plugins_gen.go
│ ├── loganalyzer/
│ ├── netmetrics/
│ └── streamprocessor/
└── tools/
└── gen/
└── main.goKeşif Aracı
tools/gen, plugins dizinindeki alt dizinleri tarar. Nokta veya alt çizgi ile başlayan dizinleri atlar, en az bir .go dosyası içerenleri eklenti kabul eder ve sonucu sıralar.
func discoverPlugins(root string) ([]string, error) {
entries, err := os.ReadDir(root)
if err != nil {
return nil, fmt.Errorf("read dir %q: %w", root, err)
}
var plugins []string
for _, entry := range entries {
if !entry.IsDir() {
continue
}
name := entry.Name()
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
continue
}
hasGo, err := dirHasGoFile(filepath.Join(root, name))
if err != nil {
return nil, err
}
if !hasGo {
continue
}
plugins = append(plugins, name)
}
sort.Strings(plugins)
return plugins, nil
}Üretilen dosya gofmt ile biçimlendirilip diske yazılır.
formatted, err := format.Source(buf.Bytes())
if err != nil {
return fmt.Errorf("gofmt generated code: %w", err)
}
if err := os.WriteFile(path, formatted, 0644); err != nil {
return fmt.Errorf("write %q: %w", path, err)
}Üretim Noktası
plugins/doc.go hem toplama noktasını hem de üretim komutunu tanımlar.
// Package plugins is the aggregation point for all plugin modules.
// Each plugin lives in its own subdirectory and registers itself via
// init(); this package imports them all via blank imports so their
// init() functions run when the binary starts.
//
// The list of imports is generated automatically by tools/gen. Do not
// edit plugins_gen.go by hand.
package plugins
//go:generate go run ../tools/gen/main.goÜretilen dosya yalnızca blank import'lardan oluşur.
// Code generated by tools/gen; DO NOT EDIT.
// Re-run with: go generate ./...
package plugins
import (
_ "log-analyzer/plugins/loganalyzer"
_ "log-analyzer/plugins/netmetrics"
_ "log-analyzer/plugins/streamprocessor"
)Alt çizgi ile yapılan import, paketten hiçbir sembol kullanılmadığını ancak paketin yine de binary'ye dahil edilip init() fonksiyonunun çalıştırılmasını istediğimizi belirtir.
Yeni bir eklenti eklemek için plugins/ altına dizin açmak, sözleşmeyi uygulamak ve üretimi tekrar çalıştırmak yeterlidir.
make generate # plugins/plugins_gen.go dosyasını yeniden üretir
make build # önce generate çalışır, sonra binary derlenirİpucu
Yeni eklenti eklendikten sonra kod üretimi çalıştırılmazsa derleme başarılı olur ancak eklenti list çıktısında görünmez. Paket hiçbir yerden import edilmediği için binary'ye girmemiş, dolayısıyla init() hiç çalışmamıştır. Bu sessiz durumu önlemek için build hedefi generate hedefine bağlanmıştır.
Ana Uygulamanın Bağlanması
Giriş noktası yalnızca iki şey yapar: eklenti toplama paketini blank import ile dahil eder ve CLI'yi çalıştırır.
package main
import (
"log-analyzer/internal/cli"
_ "log-analyzer/plugins"
)
func main() {
cli.Execute()
}main() çalışmaya başlamadan önce tüm init() fonksiyonları tamamlanır. Bu nedenle CLI kurulurken registry doludur.
func bindPlugins(root *cobra.Command) {
for _, p := range plugin.All() {
root.AddCommand(p.Command())
}
}
func newListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all registered plugins",
Run: func(cmd *cobra.Command, args []string) {
plugins := plugin.All()
if len(plugins) == 0 {
fmt.Println("No plugins registered.")
return
}
fmt.Printf("Registered plugins: %d\n\n", len(plugins))
for _, p := range plugins {
fmt.Printf(" - %-20s %s\n", p.Name(), p.Description())
}
},
}
}
func Execute() {
root := newRootCommand()
root.AddCommand(newListCommand())
bindPlugins(root)
if err := root.Execute(); err != nil {
os.Exit(1)
}
}bindPlugins fonksiyonu hiçbir eklenti adını bilmez. Döngü registry üzerinde döner ve her eklentinin kendi komut ağacını kök komuta ekler. Yeni bir eklenti eklendiğinde bu dosyada değişiklik gerekmez.
list komutu ise sistemin kendini anlatmasını sağlar. Kullanıcı hangi yeteneklerin derlendiğini çalıştırma anında görebilir.
Derleme Anı ve Çalışma Anı Keşfi
Bu tasarımda keşif derleme anında, kayıt ise süreç başlarken gerçekleşir. Eklentiler çalışma anında dosya sisteminden okunmaz.
| Aşama | Ne olur |
|---|---|
| Kod üretimi | plugins/ taranır, import listesi yazılır |
| Derleme | Eklenti paketleri binary'ye dahil edilir, arayüz uyumu denetlenir |
| Süreç başlangıcı | init() fonksiyonları Register() çağırır |
main() | All() ile registry okunur, komutlar bağlanır |
| Komut yürütme | Yalnızca kullanıcının seçtiği eklentinin komutu çalışır |
Bu yaklaşımın bedeli, yeni eklenti için yeniden derleme gerekmesidir. Karşılığında üç kazanç elde edilir: uyumsuz eklenti derleme hatası verir, dağıtılan binary tek dosyadır ve çalışma anında güvenilmeyen kod yükleme yüzeyi oluşmaz.
Go'nun standart kütüphanesindeki plugin paketi çalışma anında paylaşımlı nesne yüklemeye izin verir, ancak yalnızca belirli platformlarda çalışır ve ana uygulamayla eklentinin aynı Go sürümü ve aynı bağımlılık sürümleriyle derlenmiş olmasını gerektirir. Pratikte bu kısıtlar, dağıtımı kolaylaştırmak yerine zorlaştırır.
Çalışma anında yükleme yapan dillerde ise keşif ve doğrulama tamamen uygulamanın sorumluluğundadır. Python'da aynı sistem, modülleri tarayan ve her modülden bir fabrika fonksiyonu bekleyen bir yükleyiciyle kurulur.
from importlib import import_module
from pkgutil import iter_modules
def discover(package_name: str = "plugins") -> dict[str, object]:
package = import_module(package_name)
prefix = f"{package.__name__}."
registry: dict[str, object] = {}
for module_info in iter_modules(package.__path__, prefix):
module = import_module(module_info.name)
factory = getattr(module, "create_plugin", None)
if not callable(factory):
raise RuntimeError(f"{module_info.name}: create_plugin() bulunamadı")
plugin = factory()
registry[plugin.plugin_id] = plugin
return registryAradaki fark yalnızca söz dizimi değildir. Go sürümünde derleyicinin yaptığı arayüz denetimi, Python sürümünde elle yazılan doğrulama koduna dönüşür; gerekli metotların varlığı, sürüm uyumu ve kimlik benzersizliği tek tek sınanmak zorundadır.
Uyarı
Her iki yaklaşımda da eklenti, ana uygulamayla aynı süreçte ve aynı yetkilerle çalışır. Derlenmiş bir eklenti de, çalışma anında yüklenmiş bir modül de uygulamanın dosya, ağ ve ortam değişkeni erişimine sahiptir. Güvenilmeyen eklentiler yalnızca hata yakalayarak güvenli hale gelmez; ayrı süreç, işletim sistemi izinleri, kaynak sınırları ve doğrulanmış dağıtım politikası gerekir.
Ayrı Süreçle Yalıtım
Projedeki netmetrics eklentisi, aynı süreç kuralının pratik bir istisnasını gösterir. Paket yakalama işi Go içinde değil, Scapy kullanan ayrı bir Python sürecinde yapılır. Betik binary'ye gömülür, geçici dosyaya yazılır ve alt süreç olarak çalıştırılır.
//go:embed sniffer.py
var snifferPy []bytectx, stop := signal.NotifyContext(
context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
proc := exec.CommandContext(ctx, python, pyArgs...)
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.Stdin = os.Stdin
if err := proc.Run(); err != nil {
if ctx.Err() != nil {
return nil
}
return fmt.Errorf("scapy ajani basarisiz: %w", err)
}Bu yapı eklenti sınırını süreç sınırına dönüştürür. Yakalama ajanı çökerse ana uygulama ayakta kalır; ham soket için gereken yüksek yetki yalnızca o sürece verilir; iki taraf arasındaki bağ, paylaşılan bellek yerine açıkça tanımlı bir komut satırı ve UDP mesaj biçimidir.
Bedeli de görünürdür. Süreç başlatma maliyeti, Python ve Scapy bağımlılığı, iki tarafta ayrı tutulan sabitler. Betikteki eşik değerinin Go tarafındaki karşılığıyla aynı kalması gerektiği kod içinde not düşülmüştür; bu da süreçler arası sözleşmelerin tıpkı API'ler gibi belgelenmesi gerektiğini gösterir.
SOLID İlkeleri ile İlişki

Open Closed Principle
Ana uygulama, Plugin arayüzünü uygulayan yeni eklentileri kabul ederek genişler. Yeni bir yetenek için internal/cli/root.go dosyasına koşul eklenmez, switch büyümez, yeni import yazılmaz. Dizin açılır, sözleşme uygulanır, kod üretimi çalıştırılır.

Değişime kapalı olmak, çekirdeğin hiçbir zaman değişmeyeceği anlamına gelmez. Sözleşme değişirse, güvenlik düzeltmesi gerekirse veya yaşam döngüsü politikası değişirse çekirdek de güncellenir. İlkenin amacı, olağan genişletmelerin mevcut davranışı sürekli değiştirmesini önlemektir.
Dependency Inversion Principle
Üst seviye CLI akışı LogAnalyzerPlugin veya NetMetricsPlugin gibi somut tiplere bağlı değildir. Hem çekirdek hem eklentiler ortak Plugin arayüzüne bağımlıdır. Bağımlılığın yönü somut uygulamadan soyut sözleşmeye çevrilmiştir.

Dikkat edilmesi gereken nokta, sözleşmenin yalnızca metot imzalarından ibaret olmamasıdır. Komut adlarının çakışmaması, hata davranışı ve çıktı biçimi de sözleşmenin parçasıdır.
Single Responsibility Principle
Sorumluluklar ayrı dosyalara dağıtılmıştır:
internal/plugin/plugin.gosözleşmeyi tanımlar.internal/plugin/register.gokayıt ve doğrulamayı yönetir.tools/genkeşif ve kod üretimini yapar.internal/clikomutları bağlar ve çalıştırır.- Her eklenti kendi davranışını uygular.
Bu ayrım, bir eklentinin iş kuralı değiştiğinde registry'nin; keşif yöntemi değiştiğinde ise eklentilerin değiştirilmesini önler.
Interface Segregation Principle
Plugin arayüzü üç metottan oluşur ve her eklenti üçünü de gerçekten kullanır. Arayüze ileride yalnızca bazı eklentilerin ihtiyaç duyacağı metotlar eklenmesi, geri kalan eklentileri boş gövdeli metot yazmaya zorlar. Böyle bir ihtiyaç doğduğunda doğru çözüm, ana arayüzü büyütmek yerine isteğe bağlı ikinci bir arayüz tanımlamak ve tip doğrulamasıyla denetlemektir.
Üretim Ortamında Dikkat Edilecek Konular
Sözleşme ve Sürümleme
Eklentiler ana uygulamayla birlikte derlendiği sürece sürüm uyumsuzluğu derleme hatasıdır. Eklentilerin ayrı depolarda geliştirildiği veya ayrı dağıtıldığı bir aşamaya geçilirse arayüze sürüm bilgisi eklenmesi ve uyumsuz eklentinin kayıt aşamasında açık bir mesajla reddedilmesi gerekir. Büyük sözleşme değişikliklerinde bir geçiş dönemi veya uyumluluk adaptörü gerekebilir.
Hata Yalıtımı
Bir eklentinin hatası diğerlerinin çalışmasını durdurmamalıdır. Komut tabanlı bu tasarımda aynı anda tek bir eklenti çalıştığı için etki alanı doğal olarak dardır. Eklentilerin aynı anda çağrıldığı bir modele geçilirse her çağrı kendi hata sınırına alınmalıdır. Süre aşımı, aşırı bellek kullanımı veya süreç çökmesi gibi sorunlar aynı süreçte tam olarak yalıtılamaz; kritik bileşenler netmetrics örneğindeki gibi ayrı süreçte çalıştırılabilir.
Yükleme Sırası ve Bağımlılıklar
init() fonksiyonlarının çalışma sırası paket bağımlılıklarına göre belirlenir ve eklentiler arasında garanti edilmez. All() fonksiyonunun ada göre sıralama yapması bu belirsizliği dışarıya yansıtmaz. Eklentiler birbirine bağımlı hale gelirse bu ilişki açıkça tanımlanmalı, çevrimsel bağımlılıklar reddedilmeli ve ortak değiştirilebilir durum paylaşımından kaçınılmalıdır.
Yapılandırma
Her eklenti yalnızca kendi yapılandırma bölümüne erişmelidir. Ana uygulamanın tüm ayar nesnesini paylaşmak, eklentileri çekirdeğin ayrıntılarına bağımlı hale getirir ve yetki sınırlarını genişletir.
Gözlemlenebilirlik
Kayıtlı eklenti adı ve sürümü, başlatma süresi, çalışma süresi, hata sayısı ve devre dışı bırakma nedeni kaydedilmelidir. Log ve metriklerde eklenti adının bulunması, hatanın kaynağını ayırmayı kolaylaştırır. list komutu bu bilginin en basit biçimidir.
Test Edilebilirlik
Sözleşme açıkça tanımlandığında eklentiler ana uygulama çalıştırılmadan test edilebilir. Registry tarafında ise boş ad, tekrarlanan ad ve sıralama davranışının da sınanması gerekir. Bu yollar üretimde en çok sorun çıkaran noktalardır.
Güvenlik
Eklenti kodunun kaynağı, bütünlüğü ve yetkileri doğrulanmalıdır. İmzalı paketler, izin listeleri ve bağımlılık taraması tedarik zinciri riskini azaltabilir. Hassas işlemler, eklentiye geniş bir uygulama nesnesi vermek yerine dar yetkili servis arayüzleri üzerinden sunulmalıdır. Yüksek yetki gerektiren işler, ayrı süreçte ve yalnızca gereken izinlerle çalıştırılmalıdır.
Sonuç
Katmanlı tasarımın temel fikri sistemin her düzeyinde aynıdır: değişmesi beklenen kısmı kararlı bir arayüzün arkasına almak. Komut seti işlemci tasarımını, sistem çağrısı arayüzü kernel içini, sürücü modeli donanım ayrıntısını, extension API ise uygulama çekirdeğini korur.
Eklenti sisteminin temeli bu nedenle kodu dizinlere bölmek değil, ana uygulama ile eklentiler arasında kararlı ve sınırlı bir sözleşme kurmaktır. log-analyzer örneğinde sözleşme üç metottan ibarettir; keşif bir kod üretim aracıyla, kayıt init() fonksiyonlarıyla, bağlama ise registry üzerinde dönen tek bir döngüyle yapılır. Çekirdek hiçbir eklentinin adını bilmez.
Open/Closed sistemin yeni eklentilerle genişletilmesini, Dependency Inversion ana uygulamanın somut tipler yerine sözleşmeye bağlanmasını, Single Responsibility ise keşif, kayıt ve eklenti davranışlarının ayrı kalmasını destekler. Bu ilkeler sayesinde log analizi, ağ telemetrisi ve akış işleme gibi birbirinden tamamen farklı üç yetenek, tek bir çekirdeğin etrafında birbirini etkilemeden çalışabilir.
Kaynakça
Eklenti(Plugin) Sistemi İçin Kaynakça
- log-analyzer deposu
- Go dil belirtimi, paket başlatma ve init
- Go blog, generate ile kod üretimi
- Go plugin paketi belgeleri
- Go embed paketi belgeleri
- Go os/exec paketi belgeleri
- Cobra komut satırı kütüphanesi
- Scapy belgeleri
- Python importlib belgeleri
- Python Packaging User Guide plugin oluşturma ve keşfetme rehberi
- Linux Kernel harici modül belgeleri
- Linux Kernel kararsız iç API notu
- Microsoft Windows kernel modu HAL belgeleri
- Robert C. Martin Clean Architecture

