İçeriğe geç
academia.sh

Ders 01 / 21

Kullanıcılar, Gruplar ve Roller

Beş ekibin kurgu ölçüm ağı üzerindeki ihtiyacı üç kimlik türüyle kurulur ve karşılaştırılır: doğrudan kullanıcıya, gruba ve role verilen iznin bağlantı sayısı, aşırı yetkisi, yeni üye maliyeti ve biri ayrıldığında geride kalan izni sayılır.

İçindekiler

Dört kurs boyunca her katman kiralandı: makine, ağ, depo, işlev ve kenar. Her kiralama bir kararı sağlayıcıya devretti ve karşılığında bir kalem geri geldi. Sorulmayan tek şey, bütün bu kiralamaları kimin adına yaptığımızdı. Bu dersin sorusu şudur: kiralanan şeyin sahibi kim ve o sahiplik kaç bağlantıyla yazılıyor.

Üç yol var: izin doğrudan kişiye, kişilerin toplandığı bir gruba ya da kişiden bağımsız bir role yazılabilir. Üçü de aynı erişimi kurar; farkları sayıdadır — kaç bağlantı yazılıyor, kaç kişi ihtiyacından fazlasını alıyor, yeni biri geldiğinde nereye dokunuluyor ve biri ayrıldığında geride ne kalıyor.

  • BI1. Model, kurgusal bölgesel ölçüm ağının beş ekibini, üç ortamını ve yedi kaynak türünü taşır. Gerçek bir hesap koşturulmuyor; ölçülen şey node ile kurulan modeldir.
  • BI2. İhtiyaç tablosu elle yazıldı ve kişiden değil, (ekip, görev) çiftinden türetiliyor. Gerçekte ihtiyaç kişi başına ayrışır; burada görev başına tek olduğu için ölçülen aşırı yetki bir alt sınırdır.
  • BI3. İzin bağlantısı bu derste yalnız (kim, ne) çiftidir; hangi işlemin yapılabildiği sayılmıyor. İşlem ayrımı sonraki derste politika belgesine girer.
  • BI4. Uzun ömürlü kimlik bilgisi yaşları kendi ürettiğimiz sayı dizisinden geliyor; tohum 20260802 olarak koda yazılıdır. Dağılım kurgudur.
  • BI5. Sınırlar kurgudur ve ders içinde tanımlıdır: hesap başına altmış grup, kırk rol, kimlik başına on politika belgesi.
  • BI6. Ayrılan kişiler, otomasyonların hangi kimlikle koştuğu ve günlük işlem sayıları elle yazıldı.

Aynı biçimde bir yetki matrisi daha önce küme nesneleri üzerinde ölçülmüştü; buradaki nesne küme nesnesi değil, kiralanan hesabın kaynağıdır ve oradaki sayılar buraya taşınmaz.

Ekip, Kaynak ve İhtiyaç

// kimlik.mjs — kurgusal bolgesel olcum aginin kimlik modeli; gercek bir hesap degil, modeldir
export const ORTAM = ['gelistirme', 'hazirlik', 'uretim'];
export const EKIP_TUR = {                                // ekip -> dokundugu kaynak turleri
  toplama: ['kenar-dugum-grubu', 'olcum-deposu', 'islev-paketi', 'kayit-akisi'],
  dogrulama: ['makine-kumesi', 'islev-paketi', 'olcum-deposu', 'kayit-akisi'],
  faturalama: ['nesne-deposu', 'olcum-deposu', 'kayit-akisi'],
  saha: ['kenar-dugum-grubu', 'ag-yapilandirmasi', 'makine-kumesi'],
  pano: ['olcum-deposu', 'kayit-akisi'],
};
export const GOREV_ORTAM = {                             // gorev -> calistigi ortamlar
  gelistirici: ['gelistirme', 'hazirlik'], nobetci: ['hazirlik', 'uretim'],
  analist: ['uretim'], sorumlu: ORTAM,
};
const KADRO = {                                          // yirmi uc kisi; elle yazildi
  toplama: ['gelistirici', 'gelistirici', 'gelistirici', 'nobetci', 'analist', 'sorumlu'],
  dogrulama: ['gelistirici', 'gelistirici', 'nobetci', 'analist', 'sorumlu'],
  faturalama: ['gelistirici', 'gelistirici', 'analist', 'sorumlu'],
  saha: ['gelistirici', 'gelistirici', 'nobetci', 'nobetci', 'sorumlu'],
  pano: ['gelistirici', 'analist', 'sorumlu'],
};
export const KISI = Object.entries(KADRO).flatMap(([e, gs]) =>
  gs.map((g, i) => ({ ad: `${e}-${i + 1}`, ekip: e, gorev: g })));

export const capraz = (turler, ortamlar) =>              // (kim, ne) ciftinin "ne" tarafi
  new Set(turler.flatMap((t) => ortamlar.map((o) => `${t}@${o}`)));
export const ihtiyac = (k) => capraz(EKIP_TUR[k.ekip], GOREV_ORTAM[k.gorev]);
export const kabaGrup = (k) => capraz(EKIP_TUR[k.ekip], ORTAM);

export const OTOMASYON = [                               // ad, kimlik, kisisel mi, gunluk islem
  ['olcum-sikistirma', 'toplama-1', true, 2450], ['kenar-saglik-taramasi', 'saha-4', true, 720],
  ['gece-fatura-aktarimi', 'faturalama-1', true, 1840], ['pano-yenileme', 'pano-2', true, 96],
  ['surum-dagitimi', 'is-kimligi-1', false, 310], ['yedek-dogrulama', 'is-kimligi-2', false, 144],
].map(([ad, kimlik, kisisel, gunluk]) => ({ ad, kimlik, kisisel, gunluk }));
export const AYRILAN = ['toplama-1', 'saha-4', 'faturalama-3', 'pano-2'];
export const KISI_GUNLUK = 40;                           // kisi basina gunluk islem, kurgu
export const SINIR = { grup: 60, rol: 40, belge: 10 };   // kurgu sinirlar, ders icinde tanimli

export function* uretec(tohum) {                         // kendi uretecimiz; tohum gorunur
  let x = tohum % 2147483647;
  for (;;) { x = (x * 48271) % 2147483647; yield x; }
}

İhtiyaç iki kalemin çarpımıdır: ekip hangi kaynak türlerine dokunuyor, görev hangi ortamlarda çalışıyor. Kaba grup aynı türleri taşır ama ortam ayrımını taşımaz — bir ekibe üye olmak, o ekibin bütün kaynaklarına üç ortamda birden erişmek demektir.

Üç Kimlik Türü

// kosum1.mjs — ayni ihtiyac uc kimlik turuyle kurulur ve baglanti sayisi karsilastirilir
import { KISI, EKIP_TUR, ORTAM, ihtiyac, kabaGrup } from './kimlik.mjs';

const rolAdi = (k) => `${k.ekip}-${k.gorev}`;
const ROL = new Map(), GRUP = new Map();
for (const k of KISI) {
  if (!ROL.has(rolAdi(k))) ROL.set(rolAdi(k), ihtiyac(k));
  if (!GRUP.has(k.ekip)) GRUP.set(k.ekip, kabaGrup(k));
}
const topla = (f) => KISI.reduce((a, k) => a + f(k), 0);
const kume = (m) => [...m.values()].reduce((a, s) => a + s.size, 0);
const yeni = { ekip: 'toplama', gorev: 'gelistirici' };  // yeni gelen bir ekip uyesi

const MODEL = [
  { ad: 'dogrudan kullanici', tanim: KISI.length, ustBag: 0, kaynakBag: topla((k) => ihtiyac(k).size),
    verilen: ihtiyac, yeniYazim: ihtiyac(yeni).size },
  { ad: 'grup', tanim: GRUP.size, ustBag: KISI.length, kaynakBag: kume(GRUP),
    verilen: kabaGrup, yeniYazim: 1 },
  { ad: 'rol', tanim: ROL.size, ustBag: KISI.length, kaynakBag: kume(ROL),
    verilen: ihtiyac, yeniYazim: 1 },
];
console.log(`${KISI.length} kisi, ${Object.keys(EKIP_TUR).length} ekip, ${ORTAM.length} ortam, ` +
  `${new Set(Object.values(EKIP_TUR).flat()).size} kaynak turu`);
console.log(`gorevden turetilen ihtiyac: ${topla((k) => ihtiyac(k).size)} (kim, ne) cifti\n`);
console.log('model               tanim  kimlik bagi  kaynak bagi  toplam  etkin izin  asiri  yeni uye');
for (const m of MODEL) {
  const etkin = topla((k) => m.verilen(k).size);
  const asiri = topla((k) => [...m.verilen(k)].filter((x) => !ihtiyac(k).has(x)).length);
  console.log(`${m.ad.padEnd(19)} ${String(m.tanim).padEnd(6)} ${String(m.ustBag).padEnd(12)} ` +
    `${String(m.kaynakBag).padEnd(12)} ${String(m.ustBag + m.kaynakBag).padEnd(7)} ` +
    `${String(etkin).padEnd(11)} ${String(asiri).padEnd(6)} ${m.yeniYazim}`);
}
23 kisi, 5 ekip, 3 ortam, 7 kaynak turu
gorevden turetilen ihtiyac: 157 (kim, ne) cifti

model               tanim  kimlik bagi  kaynak bagi  toplam  etkin izin  asiri  yeni uye
dogrudan kullanici  23     0            157          157     157         0      8
grup                5      23           48           71      231         74     1
rol                 17     23           115          138     157         0      1

Yirmi üç kişinin görevlerinden çıkan ihtiyaç yüz elli yedi çifttir ve modelden bağımsızdır; değişen, o çiftlerin kaç bağlantıyla kurulduğudur. Doğrudan yazımda birebir: yüz elli yedi ihtiyaç, yüz elli yedi bağlantı. Grup yetmiş bire iniyor — kırk beş bağlantı daha az — ama etkin izin iki yüz otuz bire çıkıyor. Yetmiş dört izin, kimsenin istemediği hâlde açık duruyor.

Grubun ucuzluğu buradan geliyor: beş grup tanımı ortam ayrımını hiç yazmıyor ve ortamı yazmamanın karşılığı her üyeye üç ortamı birden vermektir. Yalnız üretimde çalışan bir analist, ekibinin bütün kaynak türlerine üç ortamda birden eriştiği için ihtiyacının tam üç katını alır.

Rol iki taraf arasında duruyor: on yedi tanım, yüz on beş kaynak bağı, yirmi üç kimlik bağı; toplam yüz otuz sekiz. Doğrudan yazımdan on dokuz bağlantı ucuz, gruptan altmış yedi bağlantı pahalı, ama aşırı yetkisi sıfır. Asıl kazancı son sütunda: yeni bir ekip üyesi geldiğinde doğrudan yazımda sekiz ayrı bağlantı kurulur, grupta ve rolde bir tane. Doğrudan yazımın maliyeti kurulumda bir kez değil, her gelişte yeniden ödenir.

Gelen, Giden ve Geride Kalan

// kosum2.mjs — ayrilan kisi, geride kalan izin ve denetim kaydinda gorunen ozne
import { KISI, AYRILAN, OTOMASYON, KISI_GUNLUK, ihtiyac, kabaGrup, uretec } from './kimlik.mjs';

const bul = (ad) => KISI.find((k) => k.ad === ad);
const kisisel = OTOMASYON.filter((o) => o.kisisel);
const bagli = new Map(kisisel.map((o) => [o.kimlik, o]));

console.log('ayrilan       gorev        ihtiyac  kaba grup izni  uzerinde kosan otomasyon');
for (const ad of AYRILAN) {
  const k = bul(ad), o = bagli.get(ad);
  console.log(`${ad.padEnd(13)} ${k.gorev.padEnd(12)} ${String(ihtiyac(k).size).padEnd(8)} ` +
    `${String(kabaGrup(k).size).padEnd(15)} ${o ? `${o.ad} (${o.gunluk} islem/gun)` : '-'}`);
}
const kapanamaz = AYRILAN.filter((a) => bagli.has(a));
const duran = kapanamaz.reduce((a, x) => a + bagli.get(x).gunluk, 0);
const kalan = (f) => kapanamaz.reduce((a, x) => a + f(bul(x)).size, 0);
console.log(`\nkimligi kapatilamayan ${kapanamaz.length} kisi, ustunde ${duran} islem/gun kosuyor`);
console.log('model               silinecek baglanti  acik kalirsa canli izin  kapatilirsa duran islem/gun');
const satir = (ad, sil, izin) => console.log(`${ad.padEnd(19)} ${String(sil).padEnd(19)} ${String(izin).padEnd(24)} ${duran}`);
satir('dogrudan kullanici', AYRILAN.reduce((a, x) => a + ihtiyac(bul(x)).size, 0), kalan(ihtiyac));
satir('grup', AYRILAN.length, kalan(kabaGrup));
satir('rol', AYRILAN.length, kalan(ihtiyac));

// Uzun omurlu kimlik bilgisi: her kimligin bir anahtari var, kisisel anahtarlar kopyalanmis
const kimlikSayisi = KISI.length + OTOMASYON.filter((o) => !o.kisisel).length;
const g = uretec(20260802);
const yas = Array.from({ length: kimlikSayisi }, () => 45 + (g.next().value % 856));
console.log(`\nuzun omurlu anahtar: ${kimlikSayisi} kimlik + ${kisisel.length} kopya = ` +
  `${kimlikSayisi + kisisel.length} saklama yeri; en eski ${Math.max(...yas)} gun, ` +
  `365 gunden eski ${yas.filter((y) => y > 365).length}`);

// Denetim kaydi: hangi satir kimin adiyla yaziliyor
const otoToplam = OTOMASYON.reduce((a, o) => a + o.gunluk, 0);
const kisiselToplam = kisisel.reduce((a, o) => a + o.gunluk, 0);
const insanToplam = KISI.length * KISI_GUNLUK;
const adTasiyan = kisiselToplam + insanToplam;
console.log(`\ngunluk denetim kaydi ${otoToplam + insanToplam} satir: insan ${insanToplam}, otomasyon ${otoToplam}`);
console.log(`kisi adiyla yazilan ${adTasiyan} satirin ${kisiselToplam} tanesini otomasyon uretti ` +
  `(%${((100 * kisiselToplam) / adTasiyan).toFixed(1)})`);
console.log(`ayrilan ${kapanamaz.length} kisinin adi otuz gunde ${30 * duran} satirda gorunmeye devam eder`);
ayrilan       gorev        ihtiyac  kaba grup izni  uzerinde kosan otomasyon
toplama-1     gelistirici  8        12              olcum-sikistirma (2450 islem/gun)
saha-4        nobetci      6        9               kenar-saglik-taramasi (720 islem/gun)
faturalama-3  analist      3        9               -
pano-2        analist      2        6               pano-yenileme (96 islem/gun)

kimligi kapatilamayan 3 kisi, ustunde 3266 islem/gun kosuyor
model               silinecek baglanti  acik kalirsa canli izin  kapatilirsa duran islem/gun
dogrudan kullanici  19                  16                       3266
grup                4                   27                       3266
rol                 4                   16                       3266

uzun omurlu anahtar: 25 kimlik + 4 kopya = 29 saklama yeri; en eski 880 gun, 365 gunden eski 16

gunluk denetim kaydi 6480 satir: insan 920, otomasyon 5560
kisi adiyla yazilan 6026 satirin 5106 tanesini otomasyon uretti (%84.7)
ayrilan 3 kisinin adi otuz gunde 97980 satirda gorunmeye devam eder

Dört kişi ayrıldı. Doğrudan yazımda on dokuz bağlantı silinmeli, grupta ve rolde dört. İkinci sütun beklenmedik olanı veriyor: dördünden üçünün kimliği kapatılamıyor, çünkü üzerinde otomasyon koşuyor. Bu üç kişinin kimliği açık kaldığı sürece doğrudan yazımda on altı, grupta yirmi yedi izin canlı kalır.

Üçüncü sütun kararın öbür yüzünü veriyor. Kimlikler kapatılırsa günde üç bin iki yüz altmış altı meşru işlem durur ve bu işlerin hiçbiri ayrılan kişilerle ilgili değil. Üç modelin hiçbiri ikilemi çözmüyor, üçünde de aynı sayı durur: kimlik türünü seçmek temizlik maliyetini ve geride kalan iznin genişliğini değiştiriyor, ama bir insanın kimliğine bağlanmış makine işini yerinden oynatmıyor.

Anahtar satırı yüzeyin boyunu veriyor. Yirmi beş kimliğin her birinde bir uzun ömürlü anahtar var, dördü otomasyon makinelerine kopyalanmış; toplam yirmi dokuz saklama yeri. On altı anahtar bir yıldan eski, en eskisi sekiz yüz seksen günlük — bir anahtarın ömrü, sızdığı anda ne kadar süre geçerli kalacağının üst sınırıdır.

Son üç satır “kim görebiliyor” sütununu kapatıyor. Kişi adı taşıyan altı bin yirmi altı denetim satırının yüzde seksen dördünü bir insan üretmedi ve ayrılan üç kişinin adı otuz gün boyunca doksan yedi bin dokuz yüz seksen satırda görünmeye devam eder. Denetim kaydı burada yanlış cevap vermiyor; sorulan soruyu yanıtlıyor ve soru yanlış.

Sınır, Kalan ve Daraltmanın Bedeli

// kosum3.mjs — sinir, kalan ve kaba grubu daraltmanin bedeli
import { KISI, EKIP_TUR, GOREV_ORTAM, ORTAM, SINIR, ihtiyac, kabaGrup, capraz } from './kimlik.mjs';

const rolAdi = (k) => `${k.ekip}-${k.gorev}`;
const rol = new Set(KISI.map(rolAdi));
const bolunmus = KISI.reduce((s, k) => {                 // her rol kendi ortamina bolunurse
  for (const o of GOREV_ORTAM[k.gorev]) s.add(`${rolAdi(k)}@${o}`);
  return s;
}, new Set());
const enCokBelge = Math.max(...KISI.map((k) => EKIP_TUR[k.ekip].length));
console.log(`kurgu sinirlar: hesap basina ${SINIR.grup} grup, ${SINIR.rol} rol; kimlik basina ${SINIR.belge} politika belgesi`);
console.log('tanim                     kullanilan  kalan');
const kal = (ad, k, s) => console.log(`${ad.padEnd(25)} ${String(k).padEnd(11)} ${s - k}`);
kal('grup (ekip basina bir)', new Set(KISI.map((k) => k.ekip)).size, SINIR.grup);
kal('rol (ekip x gorev)', rol.size, SINIR.rol);
kal('rol (ortama bolunmus)', bolunmus.size, SINIR.rol);
kal('belge (en genis kimlik)', enCokBelge, SINIR.belge);
const rolBasinaEkip = rol.size / new Set(KISI.map((k) => k.ekip)).size;
console.log(`ekip basina ${rolBasinaEkip} rol dusuyor; rol sinirina ` +
  `${Math.floor((SINIR.rol - rol.size) / rolBasinaEkip)} ekip kaldi, ortama bolunmus tanimda ` +
  `${Math.floor((SINIR.rol - bolunmus.size) / (bolunmus.size / 5))} ekip`);

// Kaba grubu en dar uyeye gore kismak: ortam kesisimi alinir
console.log('\nekip        uye  ortak ortam  uye basina kaba izin  uye basina dar izin  duran mesru izin');
let toplamDusen = 0, toplamKorunan = 0, toplamIhtiyac = 0;
for (const e of Object.keys(EKIP_TUR)) {
  const uye = KISI.filter((k) => k.ekip === e);
  const kesisim = ORTAM.filter((o) => uye.every((k) => GOREV_ORTAM[k.gorev].includes(o)));
  const dar = capraz(EKIP_TUR[e], kesisim);
  const ist = uye.reduce((a, k) => a + ihtiyac(k).size, 0);
  const kor = uye.reduce((a, k) => a + [...ihtiyac(k)].filter((x) => dar.has(x)).length, 0);
  toplamDusen += ist - kor; toplamKorunan += kor; toplamIhtiyac += ist;
  console.log(`${e.padEnd(11)} ${String(uye.length).padEnd(4)} ${String(kesisim.length || '-').padEnd(12)} ` +
    `${String(kabaGrup(uye[0]).size).padEnd(21)} ${String(dar.size).padEnd(20)} ${ist - kor}`);
}
console.log(`\ntoplam: kaba grup ${KISI.reduce((a, k) => a + kabaGrup(k).size, 0)} izin veriyor, ` +
  `daraltilmis grup ${toplamKorunan}; ${toplamIhtiyac} mesru izinden ${toplamDusen} tanesi dusuyor ` +
  `(%${((100 * toplamDusen) / toplamIhtiyac).toFixed(1)})`);
kurgu sinirlar: hesap basina 60 grup, 40 rol; kimlik basina 10 politika belgesi
tanim                     kullanilan  kalan
grup (ekip basina bir)    5           55
rol (ekip x gorev)        17          23
rol (ortama bolunmus)     35          5
belge (en genis kimlik)   4           6
ekip basina 3.4 rol dusuyor; rol sinirina 6 ekip kaldi, ortama bolunmus tanimda 0 ekip

ekip        uye  ortak ortam  uye basina kaba izin  uye basina dar izin  duran mesru izin
toplama     6    -            12                    0                    48
dogrulama   5    -            12                    0                    40
faturalama  4    -            9                     0                    24
saha        5    1            9                     3                    18
pano        3    -            6                     0                    12

toplam: kaba grup 231 izin veriyor, daraltilmis grup 15; 157 mesru izinden 142 tanesi dusuyor (%90.4)

Sınır tablosu iki ayrı hikâye anlatıyor. Grup tarafında sıkıntı yok: beş grup, elli beş yer boş. Rol tarafında on yedi rol var ve yirmi üç yer kaldı; ekip başına üç virgül dört rol düştüğüne göre altı ekip daha eklenebilir. Rolleri ortam başına bölmek — üretim yetkisini ayrı bir role koymak — tanım sayısını otuz beşe çıkarıyor ve kalan yeri beşe düşürüyor: tek başına makul görünen bir daraltma, hesabı büyümeye kapatıyor.

Alt tablo daraltmanın öbür bedelini sayıyor. Kaba grubun aşırı yetkisini gruba dokunarak kapatmak, grubu en dar üyesine göre kısmak demektir; üyelerin ortak ortamı alınır. Beş ekibin dördünde ortak ortam yok — bir geliştirici geliştirme ve hazırlıkta, bir analist yalnız üretimde çalıştığı için kesişim boş kalıyor. Kaba grubun verdiği iki yüz otuz bir izin on beşe düşüyor, ama düşenlerin yüz kırk ikisi meşru ihtiyaçtı: yüzde doksan.

Yetmiş dört aşırı izin grup tanımına dokunarak temizlenemez. Aşırı yetki grubun içinde değil, kaba oluşundadır; kapatmanın tek yolu grubu bölmektir ve grubu görev ekseninde böldüğümüzde elde ettiğimiz şey zaten rol modelidir. Grup ucuzdur çünkü ayrım yapmaz, rol pahalıdır çünkü ayrımı yazar, doğrudan yazım ise aynı ayrımı her kişi için yeniden yazar.

Özet

  • Yirmi üç kişinin görevlerinden çıkan 157 (kim, ne) çiftini doğrudan yazım 157, kaba grup 71, rol 138 bağlantıyla kurar; yeni bir üye doğrudan yazımda 8, grupta ve rolde 1 bağlantı ister.
  • Grubun 45 bağlantılık ucuzluğunun karşılığı 74 aşırı izindir: etkin izin 157’den 231’e çıkar, çünkü grup ortam ayrımını hiç yazmaz.
  • Ayrılan dört kişiden üçünün kimliği kapatılamıyor çünkü üstünde günde 3266 işlemlik otomasyon koşuyor; kimlik açık kalırsa grupta 27, doğrudan yazımda ve rolde 16 izin canlı kalır.
  • Denetim kaydında kişi adı taşıyan 6026 satırın 5106’sı bir insan tarafından üretilmedi; 25 kimliğin uzun ömürlü anahtarı 29 yerde duruyor ve 16 tanesi bir yıldan eski.
  • Kaba grubu en dar üyesine göre kısmak aşırı yetkiyi kapatır ama 157 meşru izinden 142’sini düşürür; aşırı yetki grubun içeriğinde değil, kaba oluşundadır.

Sonraki Adım

Bu dersin bağlantıları yalnız (kim, ne) çiftini taşıyor: toplama ekibinin ölçüm deposuna erişimi var. Hangi erişim olduğu yazılmadı — okumak mı, yazmak mı, deponun kendisini kaldırmak mı. O küme yazılmadığı sürece “erişimi var” cümlesi bir şey ölçmüyor. Sonraki ders bağlantının içini açar: etki, işlem, kaynak deseni ve koşul. Aynı niyetin farklı yazımlarının gerçekte kaç işlemi kapsadığı sayılır, joker karakterin genişlettiği kapsam kalem kalem çıkarılır ve açık retin neden her zaman kazandığı gösterilir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat