Miras ile bir RESTful API nasıl modellenir?


88

Bir RESTful API aracılığıyla göstermem gereken bir nesne hiyerarşim var ve URL'lerimin nasıl yapılandırılması ve ne döndürmeleri gerektiğinden emin değilim. Herhangi bir en iyi uygulama bulamadım.

Hayvanlardan miras kalan Köpeklerim ve Kedilerim olduğunu varsayalım. Köpekler ve kediler üzerinde CRUD operasyonlarına ihtiyacım var; Genel olarak hayvanlar üzerinde de operasyonlar yapabilmek istiyorum.

İlk fikrim şöyle bir şey yapmaktı:

GET /animals        # get all animals
POST /animals       # create a dog or cat
GET /animals/123    # get animal 123

Mesele şu ki, / animals koleksiyonu, tam olarak aynı yapıya sahip olmayan nesneleri (köpekler ve kediler) geri alıp alabileceği için artık "tutarsız". Farklı özniteliklere sahip nesneleri döndüren bir koleksiyona sahip olmak "RESTful" olarak kabul edilir mi?

Diğer bir çözüm, her somut tür için aşağıdaki gibi bir URL oluşturmak olabilir:

GET /dogs       # get all dogs
POST /dogs      # create a dog
GET /dogs/123   # get dog 123

GET /cats       # get all cats
POST /cats      # create a cat
GET /cats/123   # get cat 123

Ama şimdi köpeklerle kediler arasındaki ilişki kayboldu. Tüm hayvanları almak isterse, hem köpek hem de kedi kaynakları sorgulanmalıdır. URL'lerin sayısı da her yeni hayvan alt türü ile artacaktır.

Bir başka öneri de, ikinci çözümü bunu ekleyerek artırmaktı:

GET /animals    # get common attributes of all animals

Bu durumda, geri getirilen hayvanlar, yalnızca tüm hayvanlar için ortak olan, köpeğe özgü ve kediye özgü nitelikleri bırakan özellikleri içerecektir. Bu, daha az ayrıntı olmasına rağmen tüm hayvanların alınmasına izin verir. Döndürülen her nesne ayrıntılı, somut sürüme bir bağlantı içerebilir.

Herhangi bir yorum veya öneriniz var mı?

Yanıtlar:


41

Öneririm:

  • Kaynak başına yalnızca bir URI kullanma
  • Hayvanları yalnızca öznitelik düzeyinde ayırt etme

Aynı kaynağa birden fazla URI ayarlamak hiçbir zaman iyi bir fikir değildir çünkü kafa karışıklığına ve beklenmeyen yan etkilere neden olabilir. Buna göre, tek URI'niz gibi genel bir şemaya dayanmalıdır /animals.

"Temel" düzeyde tüm köpek ve kedi koleksiyonuyla uğraşmanın bir sonraki zorluğu, /animalsURI yaklaşımı sayesinde halihazırda çözülmüştür .

Köpekler ve kediler gibi özel türlerle uğraşmanın son zorluğu, medya türünüzdeki sorgu parametreleri ve tanımlama özelliklerinin bir kombinasyonu kullanılarak kolayca çözülebilir. Örneğin:

GET /animals( Accept : application/vnd.vet-services.animals+json)

{
   "animals":[
      {
         "link":"/animals/3424",
         "type":"dog",
         "name":"Rex"
      },
      {
         "link":"/animals/7829",
         "type":"cat",
         "name":"Mittens"
      }
   ]
}
  • GET /animals - tüm köpekleri ve kedileri alır, hem Rex hem de Mittens'i döndürür
  • GET /animals?type=dog - tüm köpekleri alır, sadece Rex'i döndürür
  • GET /animals?type=cat - tüm kedileri alır, sadece Mittens alır

Daha sonra, hayvanları yaratırken veya değiştirirken, dahil olan hayvanın türünü belirtmek arayanın görevi olacaktır:

Ortam türü: application/vnd.vet-services.animal+json

{
   "type":"dog",
   "name":"Fido"
}

Yukarıdaki yük, bir POSTveya PUTistek ile gönderilebilir .

Yukarıdaki şema, size REST aracılığıyla OO kalıtımı gibi temel benzer özellikleri ve büyük bir ameliyat veya URI şemanızda herhangi bir değişiklik olmaksızın daha fazla uzmanlık (yani daha fazla hayvan türü) ekleme yeteneği sağlar.


Bu, bir REST API aracılığıyla "yayınlamaya" çok benziyor. Aynı zamanda bir C ++ alt sınıfının bellek düzenindeki sorunları / çözümleri de hatırlatıyor. Örneğin, bellekte tek bir adresle hem temel hem de alt sınıfın aynı anda nerede ve nasıl temsil edileceği.
trcarden

10
GET /animals - gets all dogs and cats GET /animals/dogs - gets all dogs GET /animals/cats - gets all cats
Öneriyorum

1
İstenilen türü GET istek parametresi olarak belirtmenin yanı sıra: Bana öyle geliyor ki bunu başarmak için kabul türünü de kullanabilirsiniz. Yani: GET /animals Kabul Etapplication/vnd.vet-services.animal.dog+json
BrianT.

22
Peki ya kedi ve köpeğin her birinin kendine özgü özellikleri varsa? POSTJson iyi yazım bilgisi taşımadığından, çoğu çerçeve onu bir modele nasıl düzgün bir şekilde kaldıracağını bilemeyeceğinden, bunu operasyonda nasıl halledersiniz ? Posta davalarını nasıl ele alırsınız, örneğin [{"type":"dog","name":"Fido","playsFetch":true},{"type":"cat","name":"Sparkles","likesToPurr":"sometimes"}?
LB2

1
Ya köpeklerin ve kedilerin (çoğunlukta) farklı özellikleri varsa? örneğin # 1 SMS için bir İletişimi GÖNDERME (kime, maskeleme) ile E-posta (e-posta adresi, cc, bcc, gönderen, isHtml) veya ör. # 2 CreditCard için bir FundingSource POSTing (maskedPan, nameOnCard, Expiry) vs. bir BankAccount (bsb, accountNumber) ... yine de tek bir API kaynağı kullanır mısınız? Bu, SOLID ilkelerinin tek sorumluluğunu ihlal ediyor gibi görünüyor, ancak bunun API tasarımı için geçerli olup olmadığından emin değil ...
Ryan.Bartsch

5

Bu soru, OpenAPI'nin en son sürümünde sunulan yeni bir geliştirmenin desteğiyle daha iyi yanıtlanabilir.

OneOf, allOf, anyOf gibi anahtar kelimeleri kullanarak şemaları birleştirmek ve JSON şeması v1.0'dan beri doğrulanmış bir ileti yükü almak mümkün olmuştur.

https://spacetelescope.github.io/understanding-json-schema/reference/combining.html

Bununla birlikte, OpenAPI'de (eski Swagger), şema kompozisyonu, polimorfizmi gerçekten desteklemek için anahtar kelime ayırıcı (v2.0 +) ve oneOf (v3.0 +) tarafından geliştirilmiştir.

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition

Kalıtımınız oneOf (alt türlerden birini seçmek için) ve allOf (türü ve alt türlerinden birini birleştirmek için) kombinasyonu kullanılarak modellenebilir. Aşağıda POST yöntemi için örnek bir tanım bulunmaktadır.

paths:
  /animals:
    post:
      requestBody:
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Dog'
              - $ref: '#/components/schemas/Cat'
              - $ref: '#/components/schemas/Fish'
            discriminator:
              propertyName: animal_type
     responses:
       '201':
         description: Created

components:
  schemas:
    Animal:
      type: object
      required:
        - animal_type
        - name
      properties:
        animal_type:
          type: string
        name:
          type: string
      discriminator:
        property_name: animal_type
    Dog:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            playsFetch:
              type: string
    Cat:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            likesToPurr:
              type: string
    Fish:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            water-type:
              type: string

1
OAS'ın buna izin verdiği doğrudur. Ancak, Swagger UI'de ( bağlantı ) görüntülenecek özelliğin desteği yoktur ve bir özelliğin, kimseye gösteremiyorsanız sınırlı kullanımda olduğunu düşünüyorum.
emft

1
@emft, doğru değil. Bu cevabı yazarken, Swagger UI zaten bunu destekliyor.
Andrejs Cainikovs

Teşekkürler, bu harika çalışıyor! Şu anda Swagger UI'nin bunu tam olarak göstermediği doğru görünüyor. Modeller, alttaki Şemalar bölümünde gösterilecek ve oneOf bölümünü referans alan tüm yanıtlar bölümü kısmen kullanıcı arayüzünde gösterilecektir (yalnızca şema, örnek yok), ancak istek girişi için örnek gövde alamazsınız. Bunun için github sorunu 3 yıldır açık olduğu için bu şekilde kalması muhtemel: github.com/swagger-api/swagger-ui/issues/3803
Jethro

4

Hem köpeklerin hem de balıkların bir listesini ve başka ne varsa geri dönen / hayvanlar için giderdim:

<animals>
  <animal type="dog">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

Benzer bir JSON örneğini uygulamak kolay olmalıdır.

Müşteriler her zaman orada olan "ad" öğesine güvenebilirler (ortak bir özellik). Ancak "tür" özelliğine bağlı olarak, hayvan temsilinin bir parçası olarak başka öğeler de olacaktır.

Böyle bir listeyi döndürmede doğası gereği RESTful veya unRESTful hiçbir şey yoktur - REST, verileri temsil etmek için herhangi bir özel format öngörmez. Tek söylediği, verilerin bir miktar temsilinin olması gerektiğidir ve bu temsilin biçimi ortam türü tarafından tanımlanır (HTTP'de İçerik Türü başlığıdır).

Kullanım durumlarınızı düşünün - karışık hayvanların bir listesini göstermeniz gerekiyor mu? O zaman, karışık hayvan verilerinin bir listesini geri getirin. Sadece köpeklerin listesine mi ihtiyacınız var? Öyle bir liste yapın.

Yapmak / animals? Type = dog veya / dogs olup olmamanız, herhangi bir URL formatını öngörmeyen REST ile ilgili değildir - bu, REST kapsamı dışında bir uygulama ayrıntısı olarak bırakılır. REST yalnızca kaynakların tanımlayıcılara sahip olması gerektiğini belirtir - hangi formatta olduğuna aldırmayın.

Bir RESTful API'ye yaklaşmak için biraz hiper medya bağlantısı eklemelisiniz. Örneğin, hayvan ayrıntılarına referanslar ekleyerek:

<animals>
  <animal type="dog" href="https://stackoverflow.com/animals/123">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish" href="https://stackoverflow.com/animals/321">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

Hiper medya bağlantısı ekleyerek, istemci / sunucu bağlantısını azaltmış olursunuz - yukarıdaki durumda, URL oluşturma yükünü istemciden uzaklaştırırsınız ve sunucunun URL'leri nasıl oluşturacağına karar vermesine izin verirsiniz (tanım gereği tek otoritedir).


1

Ama şimdi köpeklerle kediler arasındaki ilişki kayboldu.

Aslında, ancak URI'nin nesneler arasındaki ilişkileri asla yansıtmadığını unutmayın.


1

Bunun eski bir soru olduğunu biliyorum, ancak RESTful kalıtım modellemesiyle ilgili diğer sorunları araştırmakla ilgileniyorum

Her zaman bir köpeğin bir hayvan ve tavuk olduğunu söyleyebilirim, ama tavuk yumurta yapar, köpek memeli ise, bu yüzden olamaz. Gibi bir API

GET animals /: animalID / yumurta

tutarlı değildir çünkü tüm hayvan alt türlerinin yumurtaları olabileceğini gösterir (Liskov ikamesinin bir sonucu olarak). Tüm memeliler bu isteğe '0' ile yanıt verirse bir geri dönüş olur, ancak ya bir POST yöntemini de etkinleştirirsem? Yarın kreplerimde köpek yumurtası olacağından korkmalı mıyım?

Bu senaryoları ele almanın tek yolu, tüm olası 'türetilmiş kaynaklar' arasında paylaşılan tüm alt kaynakları bir araya getiren bir 'süper kaynak' ve daha sonra buna ihtiyaç duyan her türetilmiş kaynak için bir uzmanlık sağlamaktır, tıpkı bir nesneyi indirdiğimizde olduğu gibi. Oop'a

GET / hayvanlar /: animalID / sons GET / tavuklar /: animalID / yumurta POST / tavuklar /: animalID / yumurta

Buradaki dezavantaj, birisinin bir tavuk koleksiyonu örneğini referans almak için bir köpek kimliğini iletebilmesidir, ancak köpek bir tavuk değildir, bu nedenle yanıt bir sebep mesajı ile 404 veya 400 ise yanlış olmaz.

Yanlış mıyım?


1
URI yapısına çok fazla önem verdiğinizi düşünüyorum. "Animals /: animalID / egg" e ulaşabilmenizin tek yolu HATEOAS kullanmaktır. Bu nedenle, hayvanı önce "animals /: animalID" aracılığıyla talep edersiniz ve sonra yumurtaları olan hayvanlar için "animals /: animalID / egg" ile bağlantı olur ve olmayanlar için ise hayvandan yumurtaya ulaşmak için bir bağlantı olun. Birisi bir şekilde yumurtası olmayan bir hayvanın yumurtasına düşerse, uygun HTTP durum kodunu (örneğin bulunamadı veya yasak)
iade edin

0

Evet yanılıyorsunuz. Ayrıca ilişkiler, OpenAPI teknik özelliklerine göre modellenebilir, örneğin bu polimorfik yolla.

Chicken:
  type: object
  discriminator:
    propertyName: typeInformation
  allOf:
    - $ref:'#components/schemas/Chicken'
    - type: object
      properties:
        eggs:
          type: array
          items:
            $ref:'#/components/schemas/Egg'
          name:
            type: string

...


Ek yorum: API yoluna odaklanmak, GET chicken/eggs denetleyiciler için ortak OpenAPI kod oluşturucuları kullanarak da çalışmalıdır, ancak bunu henüz kontrol etmedim. Belki birisi deneyebilir?
Andreas Gaus
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.