REST API kavramları


10

Birisinin biraz ışık tutacağını umduğum REST API tasarımı hakkında üç sorum var. Saatlerce durmadan aradım ama sorularıma hiçbir yerde cevap bulamadım (belki sadece ne arayacağımı bilmiyorum?).

Soru 1

İlk sorum eylemler / RPC ile ilgili. Bir süredir bir REST API geliştiriyorum ve koleksiyonlar ve kaynaklar açısından bir şeyler düşünmeye alışkınım. Ancak, paradigmanın uygulanmadığı birkaç durumla karşılaştım ve bunu REST paradigmasıyla uzlaştırmanın bir yolu olup olmadığını merak ediyorum.

Özellikle, bir kaynağı değiştirmenin bir e-postanın oluşturulmasına neden olduğu bir durumum var. Ancak, daha sonraki bir noktada kullanıcı daha önce gönderilen e-postayı yeniden göndermek istediğini belirtebilir. E-postayı yeniden gönderirken hiçbir kaynak değiştirilmez. Hiçbir durum değişmez. Bu sadece gerçekleşmesi gereken bir eylem. Eylem belirli kaynak türüne bağlıdır.

Bir tür eylem çağrısını kaynak URI ile karıştırmak uygun mudur /collection/123?action=resendEmail? Eylemi belirtmek ve kaynak kimliğini ona iletmek daha iyi olur /collection/resendEmail?id=123mu (ör. )? Bu konuda yanlış bir yol var mı? Geleneksel olarak (en azından HTTP ile) gerçekleştirilen eylem istek yöntemidir (GET, POST, PUT, DELETE), ancak bunlar bir kaynakla özel eylemlere gerçekten izin vermez.

soru 2

Bir koleksiyonu (örneğin /collection?someField=someval) sorgularken döndürülen kaynak kümesine filtre uygulamak için URL'nin sorgu dizesi bölümünü kullanıyorum . API denetleyicim içinde, bu alan ve değerle ne tür bir karşılaştırma yapacağını belirledim. Bunun gerçekten işe yaramadığını gördüm. API kullanıcı gerçekleştirmek istedikleri karşılaştırma türünü belirtmek için izin vermek için bir yol gerekir.

Ben ettik şimdiye kadar ile gelip en iyi fikir API kullanıcısı, örneğin alan adının ek bir uzantısı (olarak belirtmek için izin vermektir /collection?someField:gte=someval- bu kaynakların nereye döndürmesi gerektiğini belirtmek için someFielddaha büyüktür veya eşit (> =) neyse somevalolduğunu Bu, iyi bir fikir mi?

Soru 3

Gibi sık sık Uri o bakış şey görmek /person/123/dogsalmak için persons dogs. Genellikle böyle bir şeyden kaçındım, çünkü sonunda böyle bir URI oluşturarak aslında sadece dogsbelirli bir personID tarafından filtrelenmiş bir koleksiyona eriştiğinizi anlıyorum . Eşdeğerdir /dogs?person=123. REST URI'sinin iki düzeyden daha derin olması için iyi bir neden var mı /collection/resource_id?


10
Üç sorunuz var. Neden ayrı olarak yayınlamıyorsunuz?
anaximander

3
Bunu 3 ayrı soruya ayırmak daha iyi olurdu. Bir izleyici tüm sorulara değil tüm sorulara mükemmel bir cevap verebilir.

2
Bence hepsi birbiriyle ilişkili. Başlık biraz üst düzey, ancak bu soru birçok kişiye yardımcı olacak ve bir SE arama sırasında kolayca bulunabilir. Bu soru, yeterli oy ve madde eklendiğinde Topluluk Wiki'si olmalıdır. Bu şeyleri araştırmam haftalar sürdü.
Andrew T Finnell

1
Bunları ayrı ayrı yayınlamak daha iyi olabilir, IDK. Bununla birlikte, @AndrewFinnell'in de belirttiği gibi, soruları bir arada tutmanın iyi bir fikir olacağını düşündüm, çünkü bunlar yaşadığım REST ile ilgili en zor sorulardı ve diğer insanların cevapları bulabilmesi güzel olurdu birlikte.
Justin Warkentin

Yanıtlar:


11

Bir tür eylem çağrısını kaynak URI ile karıştırmak uygun mudur /collection/123?action=resendEmail? Eylemi belirtmek ve kaynak kimliğini ona iletmek daha iyi olur /collection/resendEmail?id=123mu (ör. )? Bu konuda yanlış bir yol var mı? Geleneksel olarak (en azından HTTP ile) gerçekleştirilen eylem istek yöntemidir (GET, POST, PUT, DELETE), ancak bunlar bir kaynakla özel eylemlere gerçekten izin vermez.

Bunu, gönderilecek e-postaları temsil eden bir kaynak koleksiyonu ile farklı bir şekilde modellemeyi tercih ederim; gönderme, ilgili kaynağın kaldırılacağı zaman içerisinde hizmetin iç kısımları tarafından işlenecektir. (Veya kullanıcı kaynağı erken silebilir ve gönderme isteğinin iptal edilmesine neden olabilir.)

Ne yaparsanız yapın, kaynak adına fiil koymayın! Bu isim (ve sorgu kısmı sıfat kümesidir). İsimsiz fiiller REST garip!

Bir koleksiyonu (örneğin /collection?someField=someval) sorgularken döndürülen kaynak kümesine filtre uygulamak için URL'nin sorgu dizesi bölümünü kullanıyorum . API denetleyicim içinde, bu alan ve değerle ne tür bir karşılaştırma yapacağını belirledim. Bunun gerçekten işe yaramadığını gördüm. API kullanıcı gerçekleştirmek istedikleri karşılaştırma türünü belirtmek için izin vermek için bir yol gerekir.

Şimdiye kadar bulduğum en iyi fikir, API kullanıcısının alan adına bir ek olarak belirtmesine izin vermektir (örneğin /collection?someField:gte=someval, someField'in ( >=) ne olursa olsun ( ) eşit veya daha büyük olduğu kaynakları döndürmesi gerektiğini belirtmek somevaliçin. Bu iyi bir fikir mi? Kötü bir fikir mi? Öyleyse, neden? Kullanıcının verilen alan ve değerle gerçekleştirilecek karşılaştırma türünü belirtmesine izin vermenin daha iyi bir yolu var mı?

Genel bir filtre yan tümcesi belirtmek ve koleksiyon içeriğini almak için herhangi bir istek üzerine isteğe bağlı bir sorgu parametresi olarak sahip. Müşteri daha sonra, istediğiniz seti tam olarak nasıl kısıtlayacağınızı belirtebilir. Ayrıca filtre / sorgu dilinin keşfedilebilirliği hakkında biraz endişe ediyorum; ne kadar zengin yaparsanız, keyfi müşterilerin keşfetmesi o kadar zor olur. En azından teorik olarak bu keşfedilebilirlik konusunu ele alan alternatif bir yaklaşım, müşterilerin toplama kaynağına getirilen kısıtlamayı açıklayan bir belgeyi POST olarak belirleyerek koleksiyonun kısıtlama alt kaynaklarının yapılmasına izin vermektir. Hala hafif bir istismar, ama en azından açıkça keşfedilebilir bir şey!

Bu tür keşfedilebilirlik, REST ile en az güçlü bulduğum şeylerden biri.

Sık sık /person/123/dogskişilerin köpeklerini almak gibi görünen URI'leri görüyorum . Genelde böyle bir şeyden kaçınırım, çünkü sonunda böyle bir URI oluşturarak aslında sadece belirli bir kişi kimliği tarafından filtrelenen bir köpek koleksiyonuna eriştiğinizi anlarım. Eşdeğerdir /dogs?person=123. Bir REST URI'sinin iki düzeyden daha derin olması için iyi bir neden var mı /collection/resource_id?

Yuvalanmış koleksiyon gerçekten dış koleksiyonun üye varlıklarının bir alt özelliği olduğunda, bunları bir alt kaynak olarak yapılandırmak mantıklıdır. “Alt özellik” derken, UML kompozisyon ilişkisi gibi bir şey kastediyorum, dış kaynağı yok etmek doğal olarak iç koleksiyonu yok etmek anlamına gelir.

Diğer toplama türleri bir HTTP yönlendirmesi olarak modellenebilir; böylece /person/123/dogsyönlendirilebilir bir 307 yaparak gerçekten yanıt verilebilir /dogs?person=123. Bu durumda, koleksiyon aslında UML kompozisyonu değil, UML toplamasıdır. Fark önemlidir; önemli!


2
Genel olarak sağlam noktalarınız var. Bununla birlikte, resendEmaileylem bir koleksiyon oluşturarak ve ona POSTing yaparak gerçekleştirilebilirken, bu daha az doğal görünüyor. Aslında, bir e-posta gönderildiğinde (gerek yok) veritabanında hiçbir şey saklamıyorum. Hiçbir kaynak değiştirilmez, bu nedenle başarılı olan veya başarısız olan bir eylemdir. Bu tür bir uygulamayı RESTful yerine bir hack haline getirerek, çağrı ömrünün ötesinde var olan bir kaynak kimliği geri veremedim. Bu sadece bir CRUD operasyonu değil.
Justin Warkentin

3

Büyük şirketlerin REST API'lerini tasarladıklarını gördüğüm tüm yollara dayanarak REST'in nasıl düzgün bir şekilde kullanılacağı konusunda biraz karışık olmak anlaşılabilir.

REST'in bir Kaynak Toplama sistemi olduğu konusunda haklısınız. Temsil Devlet Transferi anlamına gelir. Bana sorarsan iyi bir tanım değil. Ancak ana kavramlar 4 HTTP VERB ve vatansız olmaktır.

Unutulmaması gereken önemli nokta, REST ile sadece 4 VERBS olması. Bunlar GET, POST, PUT ve DELETE'dir. Sizin resendörnek REST yeni Fiil eklemek. Bu bir kırmızı bayrak olmalı.

Soru 1

REST API'nizin arayanının, PUTkoleksiyonunuzda bir gerçekleştirmenin bir e-postanın oluşturulmasına neden olacağını bilmesi gerekmediğini fark etmek önemlidir . Bu bana bir sızıntı kokuyor. Bildikleri şey, bir gerçekleştirmenin PUTdaha sonra sorgulayabilecekleri ekstra görevlerle sonuçlanabileceğidir. Bunu GETyakın zamanda oluşturulan bir kaynak üzerinde bir gerçekleştirerek bilirlerdi . Bu GET, kaynağı ve Taskonunla ilişkili tüm kaynak kimliklerini döndürür . Daha sonra durumlarını belirlemek ve hatta yeni bir görev göndermek için bu görevleri sorgulayabilirsiniz Task.

Birkaç seçeneğiniz var.

REST - Görev kaynağı tabanlı yaklaşım

tasksEylemleri gerçekleştirmek için sisteminize belirli görevleri gönderebileceğiniz bir kaynak oluşturun . Daha sonra GETgörevi, IDdurumunu belirlemek için döndürülen görevi temel alarak yapabilirsiniz .

Ya da SOAP over HTTPmimarinize RPC eklemek için bir Web Hizmeti ile karışabilirsiniz .

belirli bir kaynağın tüm görevleri için sorgulama

GET http://server/api/myCollection/123/tasks

{ "tasks" :
    [ { "22333" : "http://server/api/tasks/223333" } ] 
}

görev kaynağı örneği

PUT http://server/api/tasks

{ 
    "type" : "send-email" , 
    "parameters" : 
    { 
         "collection-type" : "foo" , 
         "collection-id" : "123" 
    } 
}

==> görevin kimliğini döndürür

223334

GET http://server/api/tasks/223334

{ 
    "status" : "complete" , 
    "date" : "whenever" 
}

REST- Eylemleri tetiklemek için POST kullanma

POSTBir kaynağa her zaman ek veriler ekleyebilirsiniz. Bence bu REST'in ruhunu ihlal edecek ama yine de uyumlu olacaktı.

Buna benzer bir POST yapabilirsiniz:

POST http://server/api/collection/123

{ "action" : "send-email" }

Toplamadan 123 kaynağını ek verilerle güncelleyeceksiniz. Bu ek veriler temel olarak arka uca bu kaynak için bir e-posta göndermesini söyleyen bir eylemdir.

Bununla ilgili sorun GETkaynak üzerinde bir bu güncellenmiş verileri döndürecek olmasıdır. Ancak, bu gereksinimlerinizi çözer ve yine de RESTful olur.

SOAP - REST'ten alınan kaynakları kabul eden Web Hizmeti

REST API'sinden önceki kaynak kimliğine göre e-posta gönderebileceğiniz yeni bir WebService oluşturun. Burada SOAP hakkında ayrıntıya girmeyeceğim, çünkü asıl soru REST ile ilgilidir ve bu iki kavram / teknoloji Elma ve Portakal olarak karşılaştırılmamalıdır .

soru 2

Burada birkaç seçeneğiniz de var:

REST API'lerini yayınlayan birçok büyük şirket, searchkaynakları döndürmek için sorgu parametrelerini iletmenin bir yolu olan bir koleksiyon ortaya koyuyor.

GET http://server/api/search?q="type = myCollection & someField >= someval"

Bu, aşağıdakiler gibi tam nitelikli REST kaynaklarının bir koleksiyonunu döndürür:

{
    "results" : 
       { [ 
             "location" : "http://server/api/myCollection/1",
             "location" : "http://server/api/myCollection/9",
             "location" : "http://server/api/myCollection/56"
         ]
       }
}

Veya bir sorgu parametresi olarak MVEL gibi bir şeye izin verebilirsiniz .

Soru 3

Geri gitmek ve bir sorgu parametresi ile diğer kaynağı sorgulamak zorunda daha alt düzeylerini tercih ederim. Ben şu ya da bu şekilde herhangi bir kural olduğuna inanmıyorum. Her iki yolu da uygulayabilir ve arayan kişinin sisteme ilk girme şekline göre hangisinin daha uygun olduğuna karar vermesini sağlayabilirsiniz.

notlar

Başkalarının okunabilirlik yorumlarına katılmıyorum. Bazılarının ne düşündüğüne rağmen REST hala insan tüketimi için değildir. Makine tüketimi içindir. Tweetlerimi görmek istersem Twitters normal web sitesini kullanırım. Onların API ile bir REST GET gerçekleştirmiyorum. Programlı olarak tweetlerimle bir şey yapmak istersem REST API'lerini kullanırım. Evet, API'lar anlaşılabilir olmalıdır, ancak gteo kadar da kötü değil, sadece sezgisel değil.

REST ile ilgili bir diğer önemli şey, API'nızdaki herhangi bir verme noktasında başlayabilmeniz ve diğer kaynakların tam URL'sini önceden bilmeden İLGİLİ diğer tüm kaynaklara gidebilmenizdir. GETREST'teki VERB sonuçları başvurduğu kaynakların tam REST URL'sini döndürmelidir. Dolayısıyla, bir Personnesnenin kimliğini döndüren bir sorgu yerine , Tam Nitelikli URL gibi döner http://server/api/people/13. Ardından, URL değişse bile sonuçlarda her zaman programlı olarak gezinebilirsiniz.

Yoruma yanıt

Gerçek dünyada aslında bir kaynak yaratma, okuma, güncelleme veya silme (CRUD) yapmayan şeyler olması gerekir.

Kaynaklar hakkında ek önlemler alınabilir. Tipik ilişkisel veritabanları Saklı Yordamlar kavramını destekler. Bunlar, bir veri kümesi üzerinde yürütülebilen ek komutlardır. REST'in doğası gereği bu konsepti yoktur. Ve yapması için hiçbir neden yok. Bu tür eylemler RPC veya SOAP Web Hizmetleri için mükemmeldir.

REST API'lerinde gördüğüm genel sorun budur. Geliştiriciler, REST'i çevreleyen kavramsal sınırlamaları sevmiyorlar, bu yüzden onu istedikleri gibi uyarlıyorlar. Bu olsa RESTful bir hizmet olmaktan keser. Esasen bu URL'ler GETsözde REST benzeri sunucu uygulamalarına çağrı yapar.

Birkaç seçeneğiniz var:

  • Görev kaynağı oluşturma
  • POSTBir eylem gerçekleştirmek için kaynağa ek verilerin desteklenmesi .
  • Ek komutları bir SOAP Web Hizmeti aracılığıyla ekleyin.

E-postayı yeniden göndermek için hangi HTTP VERB'yi kullanırsanız bir sorgu parametresi kullandıysanız?

  • GET- Bu, e-postayı yeniden gönderiyor mu ve kaynağın verilerini döndürüyor mu? Bir sistem söz konusu URL'yi önbelleğe alıp söz konusu kaynağın benzersiz URL'si gibi davranırsa ne olur? URL'yi her vurduklarında bir e-postayı yeniden gönderir.
  • POST - Kaynağa yeni bir veri göndermediniz, sadece ek bir sorgu parametresi.

Verilen tüm gereksinimlere bağlı POSTolarak, kaynak action fieldolarak POST verisi ile bir kaynak yapmak sorunu çözecektir.


3
HTTP aracılığıyla uygulanan REST size bu 4 fiili verirken, bu fiillerin sonu olması gerektiğine ikna olmadım. Gerçek dünyada aslında bir kaynak yaratma, okuma, güncelleme veya silme (CRUD) yapmayan şeyler olması gerekir. Bir e-postayı yeniden göndermek bunlardan biridir. Veritabanında hiçbir şey depolamam veya değiştirmem gerekmiyor. Bu sadece başarılı olan veya başarısız olan bir eylemdir.
Justin Warkentin

@ JustinWarkentin İhtiyaçlarınızın ne olduğunu anlıyorum. Ancak bu, REST'i olmadığı bir şey yapmaz. URL'ye yeni bir fiil eklemek REST mimarisine aykırıdır. Yanıtımı RESTful olacak başka bir alternatif sunacak şekilde güncelleyeceğim.
Andrew T Finnell

@JustinWarkentin Cevabımda 'REST - Eylemleri tetiklemek için POST kullanma' konusuna bakın.
Andrew T Finnell

0

Soru 1: Bir tür eylem çağrısını bir kaynak URI ile karıştırmak uygun mudur [veya] eylemi belirtmek ve kaynak kimliğini ona iletmek daha iyi olur mu?

İyi soru. Bu durumda, ikinci yaklaşımı kullanmanızı, eylemi belirtmenizi ve buna bir kaynak kimliği iletmenizi öneririm. Bu şekilde, kaynağınız ilk kez değiştirildiğinde, /sendEmaileylemi (yan not: buna "yeniden gönderme" demeye gerek yoktur) ayrı bir RESTful isteği olarak (değiştirilen kaynaktan bağımsız olarak daha sonra tekrar çağırabileceğiniz) çağırır. ).

Soru 2: Aşağıdaki gibi bir karşılaştırma operatörünün kullanımına ilişkin:/collection?someField:gte=someval

Bu teknik olarak iyi olsa da, muhtemelen kötü bir fikirdir. REST'in temel ilkelerinden biri okunabilirliktir. Ben sadece karşılaştırma operatörünü başka bir parametre olarak geçirmenizi öneririm, örneğin: /collection?someField=someval&operator=gteve elbette API'nızı varsayılan bir durum için ( operatorparametrenin URI dışında bırakılması durumunda) sunacak şekilde tasarlayın .

Soru 3: Bir REST URI'sının iki seviyeden daha derin olması için gerçekten iyi bir neden var mı?

Evet; soyutlama için. : Ben örneğin, çoklu URI seviyeleri ile soyutlama katmanları kullanmak birkaç REST API gördüm /vehicles/cars/123ya /vehicles/bikes/123da ikisiyle ilgili faydalı bilgiler ile çalışmak size izin veren /vehiclesve /vehicles/bikeskoleksiyonları. Bunu söyledikten sonra, ben bu yaklaşımın büyük bir hayranı değilim; bunu pratikte nadiren yapmanız gerekir ve API'yı yalnızca 2 düzey kullanacak şekilde yeniden tasarlayabilirsiniz.

Ve evet, yukarıdaki yorumların önerdiği gibi, gelecekte sorularınızı ayrı yayınlara bölmek en iyisi olacaktır;)


Soru 2'ye ilişkin örneğimin çok basit olduğunu düşünüyorum. Ben sadece bir değil, toplama filtre için kullanılan her alan için bir karşılaştırma operatörü belirtmek gerekir, bu yüzden örnekte böyle bir şey olması gerekir /collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq.
Justin Warkentin

0

2. soru için farklı bir alternatif daha esnek olabilir: her bir aramayı kullanıcının kullanmadan önce oluşturduğu bir kaynak olarak düşünün.

diyelim ki bir "aramalar" konteynırınız var, orada POST /api/searches/içerikte sorgu belirtimi olan bir. sizin için daha kolay olan bir JSON, XML, hatta bir SQL belgesi olabilir. Sorgu doğru ayrıştırılırsa, kendi URI'sı ile yeni bir kaynak olarak yeni bir arama oluşturulur./api/searches/q123/

Sonra, istemci sadece GET /api/searches/q123/sorgu sonuçlarını almak için yapabilirsiniz .

Son olarak, istemciden sorguyu silmesini isteyebilir veya oturumu kapattıktan sonra temizleyebilirsiniz.


0

Bir çeşit eylem çağrısını kaynak URI ile karıştırmak uygun mudur (örn. / Collection / 123? Action = resendEmail)? Eylemi belirtmek ve kaynak kimliğini ona iletmek daha iyi olur (örneğin / collection / resendEmail? İd = 123)? Bu konuda yanlış bir yol var mı? Geleneksel olarak (en azından HTTP ile) gerçekleştirilen eylem istek yöntemidir (GET, POST, PUT, DELETE), ancak bunlar bir kaynakla özel eylemlere gerçekten izin vermez.

IRI'ler işlemleri değil kaynakları tanımlamak için olduğu için uygun değildir (ancak POST olmayan ve GET yöntemlerinin kullanılmadığı durumlarda bu yöntemi bir süre geçersiz kılma yaklaşımını kullanın ). Yapabileceğiniz uygun bir HTTP yöntemi aramak veya yeni bir yöntem oluşturmaktır. POST bu durumlarda arkadaşınız olabilir (uygun bir yöntem bulamazlarsa ve istek alınmazsa ppl kullanın). E-posta göndermeden kaynak yapmak için başka bir yaklaşım ve böylece POST /emailsgerçek bir kaynak oluşturmadan postaları gönderebilirsiniz. Btw. URI yapısı anlambilim içermez, bu nedenle REST perspektifinden ne tür URI'ler kullandığınız önemli değildir. Önemli olan, istemcilere gönderdiğiniz bağlantılara atanan meta verilerdir (örn. Bağlantı ilişkisi ).

Şimdiye kadar bulduğum en iyi fikir, API kullanıcısının alan adına bir ek olarak belirtmesine izin vermektir (örn. / Collection? SomeField: gte = someval - someField öğesinin daha büyük olduğu yerlerde kaynak döndürmesi gerektiğini belirtmek için ya da (> =) ne olursa olsun, bu iyi bir fikir mi? Kötü bir fikir mi? Öyleyse, neden? Kullanıcının verilen alan ve değerle gerçekleştirilecek karşılaştırma türünü belirtmesine izin vermenin daha iyi bir yolu var mı?

Kendi sorgu dilinizi oluşturmanız gerekmez. Ben zaten var olan birini kullanmak ve bağlantı meta-veri bazı sorgu açıklama eklemek istiyorum. Bunu yapmak için muhtemelen bir RDF ortam türü (örn. JSON-LD) kullanmanız veya özel bir MIME türü kullanmanız gerekir (afaik, bunu destekleyen RDF olmayan bir biçim yoktur). Mevcut standartları kullanmak istemcinizi sunucudan ayırır, tek biçimli arabirim kısıtlaması budur.

/ Dogs? Person = 123'e eşit olacaktır. Bir REST URI'sinin iki düzeyden daha derin olması için iyi bir neden var mı (/ collection / resource_id)?

Daha önce de belirttiğim gibi, URI yapısı bir REST perspektifinden önemli değildir. /x71fd823df2Örneğin kullanabilirsiniz . URI yapısını değil, bağlantılara atanan meta verileri kontrol ettikleri için istemcilere yine de mantıklı gelir. URI'nin temel amacı kaynakları tanımlamaktır. URI standardında, yolun hiyerarşik veriler içerdiğini ve sorgunun hiyerarşik olmayan veriler içerdiğini belirtirler. Ancak hiyerarşik olanın çok öznel olduğu söylenebilir. Bu yüzden birden fazla düzey derin URI ve URI ile uzun sorgularla tanışıyorsunuz.

Saatlerce durmadan aradım ama sorularıma hiçbir yerde cevap bulamadım (belki sadece ne arayacağımı bilmiyorum?).

Fielding tezinden , HTTP standardından ve Markus'un 3. nesil web API'lerinden en az REST kısıtlamalarını okumalısınız .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.