İçeriğe geç

Kitaplar / Yöntem Birimler ve Araçlar Kılavuzu / 9. Üç katmanlı bir uygulama

9. Üç katmanlı bir uygulama

Sunucu ve Veritabanı ile klasik üç katmanlı yapı Yöntem’in birimleriyle doğrudan kurulur: her katman ayrı bir dosyadır ve yalnızca bir altındakini kullan ile görür.

Katman Dosya Bildiği
Sunum 10_uc_katman.y (program) HTTP: yollar, JSON, durum kodları
İş UrunIsleri.y (birim) Kurallar: ad, fiyat ve stok denetimi
Depolama UrunDeposu.y (birim) SQL: tablo ve sorgular

Depolama katmanı yalnızca SQL içerir. arayüz bölümü üst katmana ne sunduğunu söyler; vt değişkeni uygulama bölümünde olduğu için dışarıdan görünmez:

Yöntem
birim ÜrünDeposu
{ Depolama katmanı: yalnızca SQL bilir. Kural da ekran da içermez.
  Başka bir veritabanına geçmek için Aç'a verilen adresi değiştirmek yeter. }
kullan Veritabanı

arayüz
  prosedür Aç(adres: metin)
  fonksiyon Hepsi: liste
  fonksiyon Bul(no: tamsayı): herhangi
  fonksiyon AdVarMı(ad: metin): mantıksal
  fonksiyon Ekle(ad: metin; fiyat: ondalık; adet: tamsayı): tamsayı
  prosedür AdetDeğiştir(no, fark: tamsayı)

uygulama

değişken
  vt: herhangi

prosedür Aç(adres: metin)
başla
  vt := Bağlan(adres)
  vt.Çalıştır('CREATE TABLE IF NOT EXISTS urun (no INTEGER PRIMARY KEY, ad TEXT NOT NULL UNIQUE, fiyat REAL NOT NULL, adet INTEGER NOT NULL)')
bitir

fonksiyon Hepsi: liste
başla
  dön vt.Sorgula('SELECT no, ad, fiyat, adet FROM urun ORDER BY no')
bitir

fonksiyon Bul(no: tamsayı): herhangi
başla
  dön vt.TekSatır('SELECT no, ad, fiyat, adet FROM urun WHERE no = ?', [no])
bitir

fonksiyon AdVarMı(ad: metin): mantıksal
başla
  dön vt.TekDeğer('SELECT COUNT(*) FROM urun WHERE ad = ?', [ad]) > 0
bitir

fonksiyon Ekle(ad: metin; fiyat: ondalık; adet: tamsayı): tamsayı
başla
  vt.Çalıştır('INSERT INTO urun (ad, fiyat, adet) VALUES (?, ?, ?)', [ad, fiyat, adet])
  dön vt.SonKimlik
bitir

prosedür AdetDeğiştir(no, fark: tamsayı)
başla
  vt.Çalıştır('UPDATE urun SET adet = adet + ? WHERE no = ?', [fark, no])
bitir

bitir.

İş katmanı kuralları uygular. HTTP durum kodu da SQL de bilmez; kural çiğnenince türü belli bir hata fırlatır:

Yöntem
birim Ürünİşleri
{ İş katmanı: kurallar burada. HTTP'yi de SQL'i de bilmez; depolama katmanını çağırır,
  kural çiğnenirse açıklayıcı bir hata fırlatır. }
kullan ÜrünDeposu

arayüz
  fonksiyon ÜrünListesi: liste
  fonksiyon ÜrünEkle(ad: metin; fiyat: ondalık; adet: tamsayı): sözlük
  fonksiyon Sat(no, adet: tamsayı): sözlük
  fonksiyon StokDeğeri: ondalık

uygulama

fonksiyon ÜrünListesi: liste
başla
  dön Hepsi
bitir

fonksiyon ÜrünEkle(ad: metin; fiyat: ondalık; adet: tamsayı): sözlük
değişken temiz: metin
başla
  temiz := Kırp(ad)
  eğer Uzunluk(temiz) < 2 ise fırlat Hata('Ürün adı en az 2 harf olmalı', 'KuralHatası')
  eğer fiyat <= 0 ise fırlat Hata('Fiyat sıfırdan büyük olmalı', 'KuralHatası')
  eğer adet < 0 ise fırlat Hata('Adet eksi olamaz', 'KuralHatası')
  eğer AdVarMı(temiz) ise fırlat Hata('Bu adda bir ürün zaten var: ' + temiz, 'KuralHatası')
  dön Bul(Ekle(temiz, fiyat, adet))
bitir

fonksiyon Sat(no, adet: tamsayı): sözlük
değişken ürün: herhangi
başla
  ürün := Bul(no)
  eğer ürün = boş ise fırlat Hata('Böyle bir ürün yok', 'BulunamadıHatası')
  eğer adet < 1 ise fırlat Hata('Satış adedi en az 1 olmalı', 'KuralHatası')
  eğer ürün['adet'] < adet ise fırlat Hata('Stok yetersiz: ' + ürün['adet'] + ' adet var', 'KuralHatası')
  AdetDeğiştir(no, -adet)
  dön ['ürün': ürün['ad'], 'adet': adet, 'tutar': ürün['fiyat'] * adet, 'kalan': ürün['adet'] - adet]
bitir

fonksiyon StokDeğeri: ondalık
değişken
  toplam: ondalık
  ü: sözlük
başla
  toplam := 0
  Hepsi içindeki her ü için toplam := toplam + ü['fiyat'] * ü['adet']
  dön toplam
bitir

bitir.

Sunum katmanı isteği iş katmanının çağrısına, iş katmanının hatasını da durum koduna çevirir:

Yöntem
program ÜçKatman
{ Sunum katmanı: HTTP isteklerini iş katmanına çevirir, sonucu JSON olarak döndürür.
  Kural bilmez, SQL bilmez; yalnızca Ürünİşleri birimini kullanır.
  Sunucu olarak çalıştırmak için:  yontem 10_uc_katman.y dinle }
kullan Sunucu, Veri, Sistem, Ürünİşleri, ÜrünDeposu

değişken
  y: sözlük

{ İş katmanının hatalarını HTTP durum kodlarına çevirir }
fonksiyon HataYanıtı(h: herhangi): herhangi
başla
  eğer h.tür = 'KuralHatası' ise dön JsonYanıt(['hata': h.mesaj], 400)
  eğer h.tür = 'BulunamadıHatası' ise dön JsonYanıt(['hata': h.mesaj], 404)
  fırlat h
bitir

fonksiyon Listele(istek: sözlük): herhangi
başla
  dön JsonYanıt(['ürünler': ÜrünListesi, 'stokDeğeri': StokDeğeri])
bitir

fonksiyon YeniÜrün(istek: sözlük): herhangi
değişken v: sözlük
başla
  dene
    v := JSONÇöz(istek['gövde'])
    dön JsonYanıt(ÜrünEkle(v['ad'], v['fiyat'], v['adet']), 201)
  yakala (h)
    dön HataYanıtı(h)
  bitir
bitir

fonksiyon Satış(istek: sözlük): herhangi
başla
  dene
    dön JsonYanıt(Sat(Tamsayı(istek['parametreler']['no']), Tamsayı(istek['sorgu'].Al('adet', '1'))))
  yakala (h)
    dön HataYanıtı(h)
  bitir
bitir

prosedür Göster(başlık: metin; y: sözlük)
başla
  yaz(başlık + ' → ' + y['durum'] + ' ' + y['gövde'], 1)
bitir

başla
  Yol('GET', '/urunler', Listele)
  Yol('POST', '/urunler', YeniÜrün)
  Yol('POST', '/urunler/:no/sat', Satış)

  eğer Uzunluk(Argümanlar) > 0 ise
  başla
    Aç('sqlite:magaza.db')
    Dinle(8080)
    dön
  bitir

  { Argümansız çalıştırınca: bellekteki veritabanıyla, ağ olmadan bir tur }
  Aç('sqlite::bellek:')
  Göster('ekle', İstekİşle('POST', '/urunler', '{"ad": "defter", "fiyat": 12.5, "adet": 40}'))
  Göster('ekle', İstekİşle('POST', '/urunler', '{"ad": " kalem ", "fiyat": 4.0, "adet": 3}'))
  Göster('aynı ad', İstekİşle('POST', '/urunler', '{"ad": "defter", "fiyat": 1.0, "adet": 1}'))
  Göster('kötü fiyat', İstekİşle('POST', '/urunler', '{"ad": "silgi", "fiyat": 0.0, "adet": 1}'))
  Göster('sat', İstekİşle('POST', '/urunler/1/sat?adet=4'))
  Göster('stok yetersiz', İstekİşle('POST', '/urunler/2/sat?adet=5'))
  Göster('ürün yok', İstekİşle('POST', '/urunler/9/sat'))
  Göster('liste', İstekİşle('GET', '/urunler'))
bitir.
Çıktı
ekle → 201 {"no": 1, "ad": "defter", "fiyat": 12.5, "adet": 40}
ekle → 201 {"no": 2, "ad": "kalem", "fiyat": 4.0, "adet": 3}
aynı ad → 400 {"hata": "Bu adda bir ürün zaten var: defter"}
kötü fiyat → 400 {"hata": "Fiyat sıfırdan büyük olmalı"}
sat → 200 {"ürün": "defter", "adet": 4, "tutar": 50.0, "kalan": 36}
stok yetersiz → 400 {"hata": "Stok yetersiz: 3 adet var"}
ürün yok → 404 {"hata": "Böyle bir ürün yok"}
liste → 200 {"ürünler": [{"no": 1, "ad": "defter", "fiyat": 12.5, "adet": 36}, {"no": 2, "ad": "kalem", "fiyat": 4.0, "adet": 3}], "stokDeğeri": 462.0}

Bu ayrımın kazandırdıkları şunlardır:

Katman sınırını dil zorlamaz: sunum katmanının kullan ÜrünDeposu yazıp doğrudan sorgu çalıştırmasını engelleyen bir şey yoktur (örnekte yalnızca veritabanını açmak için kullanılıyor). Sınırı korumak, her birimin arayüz bölümünü küçük tutmakla başlar.