Mikro hizmetler için API'ler tasarlama

Azure DevOps

Hizmetler arasındaki tüm veri alışverişleri iletiler veya API çağrıları aracılığıyla gerçekleştiğinden, mikro hizmetler mimarisinde iyi API tasarımı önemlidir. Gevevelemeli G/Ç oluşturmamak için API'lerin verimli olması gerekir. Hizmetler bağımsız çalışan ekipler tarafından tasarlandığından, güncelleştirmelerin diğer hizmetleri bozmaması için API'lerin iyi tanımlanmış semantiklere ve sürüm oluşturma düzenlerine sahip olması gerekir.

Mikro hizmetler için API tasarımı

İki tür API arasında ayrım yapmak önemlidir:

  • İstemci uygulamalarının çağırdığını genel API'ler.
  • Hizmetler arası iletişim için kullanılan arka uç API'leri.

Bu iki kullanım örneğinin gereksinimleri biraz farklıdır. Genel API'nin istemci uygulamalarıyla, genellikle tarayıcı uygulamalarıyla veya yerel mobil uygulamalarla uyumlu olması gerekir. Çoğu zaman bu, genel API'nin HTTP üzerinden REST kullanacağı anlamına gelir. Ancak arka uç API'leri için ağ performansını hesaba katmalısınız. Hizmetlerinizin ayrıntı düzeyine bağlı olarak, hizmetler arası iletişim çok fazla ağ trafiğine neden olabilir. Hizmetler hızla G/Ç'ye bağlanabilir. Bu nedenle serileştirme hızı ve yük boyutu gibi konular daha önemli hale gelir. REST'i HTTP üzerinden kullanmanın bazı popüler alternatifleri gRPC, Apache Avro ve Apache Thrift'tir. Bu protokoller ikili serileştirmeyi destekler ve genellikle HTTP'den daha verimlidir.

Dikkat edilmesi gerekenler

API'nin nasıl uygulanabileceğini seçerken göz atın.

REST ile RPC karşılaştırması. REST stili arabirim ile RPC stili bir arabirim arasındaki dengeleri göz önünde bulundurun.

  • REST modelleri, etki alanı modelinizi ifade etmenin doğal bir yolu olabilir. HTTP fiillerini temel alan tekdüzen bir arabirim tanımlar ve bu da geliştirilebilirliği teşvik eder. Bir kez etkililik, yan etkiler ve yanıt kodları açısından iyi tanımlanmış semantiklere sahiptir. Ayrıca durum bilgisi olmayan iletişimi zorlayarak ölçeklenebilirliği artırır.

  • RPC, işlemler veya komutlar etrafında daha odaklıdır. RPC arabirimleri yerel yöntem çağrılarına benzediğinden, aşırı geveze API'ler tasarlamanıza yol açabilir. Ancak bu, RPC'nin gevende olması gerektiği anlamına gelmez. Yalnızca arabirimi tasarlarken dikkatli olmanız gerektiği anlamına gelir.

RESTful arabirimi için en yaygın seçenek JSON kullanarak HTTP üzerinden REST'tir. RPC stili bir arabirim için gRPC, Apache Avro ve Apache Thrift gibi çeşitli popüler çerçeveler vardır.

Verimlilik. Hız, bellek ve yük boyutu açısından verimliliği göz önünde bulundurun. GRPC tabanlı arabirim genellikle HTTP üzerinden REST'ten daha hızlıdır.

Arabirim tanım dili (IDL). BIR API'nin yöntemlerini, parametrelerini ve dönüş değerlerini tanımlamak için BIR IDL kullanılır. idl istemci kodu, serileştirme kodu ve API belgeleri oluşturmak için kullanılabilir. IDL'ler Postman gibi API test araçları tarafından da kullanılabilir. gRPC, Avro ve Thrift gibi çerçeveler kendi IDL belirtimlerini tanımlar. HTTP üzerinden REST'in standart bir IDL biçimi yoktur, ancak yaygın bir seçenek OpenAPI'dir (eski adıyla Swagger). Resmi bir tanım dili kullanmadan http REST API'sini de oluşturabilirsiniz, ancak ardından kod oluşturma ve test etme avantajlarını kaybedersiniz.

Serileştirme. Nesneler kablo üzerinde nasıl serileştirilir? Seçenekler arasında metin tabanlı biçimler (öncelikli olarak JSON) ve protokol arabelleği gibi ikili biçimler bulunur. İkili biçimler genellikle metin tabanlı biçimlerden daha hızlıdır. Ancak çoğu dil ve çerçeve JSON serileştirmeyi desteklediğinden JSON birlikte çalışabilirlik açısından avantajlara sahiptir. Bazı serileştirme biçimleri sabit bir şema gerektirir ve bazıları bir şema tanım dosyası derlemeyi gerektirir. Bu durumda, bu adımı derleme sürecinize eklemeniz gerekir.

Çerçeve ve dil desteği. HTTP neredeyse her çerçevede ve dilde desteklenir. gRPC, Avro ve Thrift'in tümü C++, C#, Java ve Python kitaplıklarına sahiptir. Thrift ve gRPC de Go'yu destekler.

Uyumluluk ve birlikte çalışabilirlik. gRPC gibi bir protokol seçerseniz genel API ile arka uç arasında bir protokol çeviri katmanına ihtiyacınız olabilir. Bir ağ geçidi bu işlevi gerçekleştirebilir. Hizmet ağı kullanıyorsanız hangi protokollerin hizmet ağıyla uyumlu olduğunu göz önünde bulundurun. Örneğin, Linkerd'in HTTP, Thrift ve gRPC için yerleşik desteği vardır.

Temel önerimiz, ikili protokolün performans avantajlarına ihtiyacınız yoksa HTTP yerine REST'i seçmenizdir. HTTP üzerinden REST için özel kitaplık gerekmez. Çağıranların hizmetle iletişim kurmak için istemci saptamasına ihtiyacı olmadığından çok az bağlama oluşturur. RESTful HTTP uç noktalarının şema tanımlarını, testlerini ve izlenmesini destekleyen zengin araç ekosistemleri vardır. Son olarak, HTTP tarayıcı istemcileriyle uyumludur, bu nedenle istemci ile arka uç arasında bir protokol çeviri katmanına ihtiyacınız yoktur.

Ancak, HTTP yerine REST'i seçerseniz, senaryonuz için yeterli performans gösterip göstermediğini doğrulamak için geliştirme sürecinin başlarında performans ve yük testi yapmanız gerekir.

RESTful API tasarımı

RESTful API'leri tasarlamak için birçok kaynak vardır. Yararlı bulabileceğiniz bazı şeyler şunlardır:

Dikkate alınması gereken bazı önemli noktalar aşağıdadır.

  • İç uygulama ayrıntılarını sızdıran veya yalnızca bir iç veritabanı şemasını yansıtan API'lere dikkat edin. API' nin etki alanını modellemesi gerekir. Bu hizmetler arasındaki bir sözleşmedir ve ideal olarak yalnızca yeni işlevler eklendiğinde değişmelidir, yalnızca bazı kodları yeniden düzenlediğiniz veya bir veritabanı tablosunu normalleştirdiğiniz için değişmemelidir.

  • Mobil uygulama ve masaüstü web tarayıcısı gibi farklı istemci türleri farklı yük boyutları veya etkileşim desenleri gerektirebilir. Her istemci için ayrı arka uçlar oluşturmak ve bu istemci için en uygun arabirimi kullanıma sunan Ön Uçlar için Arka Uçlar desenini kullanmayı göz önünde bulundurun.

  • Yan etkileri olan işlemler için bunları bir kez etkili hale getirmeyi ve PUT yöntemleri olarak uygulamayı göz önünde bulundurun. Bu, güvenli yeniden denemeleri etkinleştirir ve dayanıklılığı geliştirebilir. Hizmetler arası iletişim makalesi bu sorunu daha ayrıntılı olarak ele alır.

  • HTTP yöntemleri, yöntemin hemen yanıt döndürdüğü ancak hizmetin işlemi zaman uyumsuz olarak gerçekleştirdiği zaman uyumsuz semantiklere sahip olabilir. Bu durumda yöntemin, isteğin işlenmek üzere kabul edildiği ancak işlemenin henüz tamamlanmadığını belirten bir HTTP 202 yanıt kodu döndürmesi gerekir. Daha fazla bilgi için bkz. Zaman uyumsuz Request-Reply düzeni.

REST'i DDD desenlerine eşleme

Varlık, toplama ve değer nesnesi gibi desenler, etki alanı modelinizdeki nesnelere belirli kısıtlamaları yerleştirecek şekilde tasarlanmıştır. DDD'nin birçok tartışmasında, desenler oluşturucular veya özellik oluşturucular ve ayarlayıcılar gibi nesne odaklı (OO) dil kavramları kullanılarak modellenir. Örneğin, değer nesnelerinin sabit olması gerekir. Bir OO programlama dilinde, oluşturucudaki değerleri atayarak ve özellikleri salt okunur hale getirerek bunu zorunlu kılabilirsiniz:

export class Location {
    readonly latitude: number;
    readonly longitude: number;

    constructor(latitude: number, longitude: number) {
        if (latitude < -90 || latitude > 90) {
            throw new RangeError('latitude must be between -90 and 90');
        }
        if (longitude < -180 || longitude > 180) {
            throw new RangeError('longitude must be between -180 and 180');
        }
        this.latitude = latitude;
        this.longitude = longitude;
    }
}

Geleneksel monolitik bir uygulama oluştururken bu tür kodlama uygulamaları özellikle önemlidir. Büyük bir kod tabanında, birçok alt sistem nesnesini kullanabilir Location , bu nedenle nesnenin doğru davranışı zorlaması önemlidir.

Bir diğer örnek de uygulamanın diğer bölümlerinin veri deposuna doğrudan okuma veya yazma işlemi yapmamasını sağlayan Depo düzenidir:

İnsansız Hava Aracı deposunun diyagramı.

Ancak mikro hizmetler mimarisinde hizmetler aynı kod tabanını paylaşmaz ve veri depolarını paylaşmaz. Bunun yerine API'ler aracılığıyla iletişim kurarlar. Scheduler hizmetinin İnsansız Hava Aracı hizmetinden bir insansız hava aracı hakkında bilgi istediği durumu göz önünde bulundurun. İnsansız Hava Aracı hizmeti, kod aracılığıyla ifade edilen bir insansız hava aracı iç modeline sahiptir. Ancak Zamanlayıcı bunu görmüyor. Bunun yerine, insansız hava aracı varlığının bir gösterimini (belki de bir HTTP yanıtında JSON nesnesi) geri alır.

Bu örnek, uçak ve havacılık endüstrileri için idealdir.

İnsansız hava aracı hizmetinin diyagramı.

Scheduler hizmeti, İnsansız Hava Aracı hizmetinin iç modellerini değiştiremez veya İnsansız Hava Aracı hizmetinin veri deposuna yazamaz. Bu, İnsansız Hava Aracı hizmetini uygulayan kodun geleneksel monolite kodlarla karşılaştırıldığında daha küçük bir açık yüzey alanına sahip olduğu anlamına gelir. drone hizmeti bir Location sınıfı tanımlarsa, bu sınıfın kapsamı sınırlıdır; başka hiçbir hizmet sınıfı doğrudan kullanmaz.

Bu nedenlerden dolayı, bu kılavuz taktiksel DDD desenleriyle ilgili kodlama uygulamalarına fazla odaklanmaz. Ancak, REST API'leri aracılığıyla DDD desenlerinin çoğunu da modelleyebileceğiniz ortaya çıktı.

Örneğin:

  • Toplamlar REST'teki kaynaklarla doğal olarak eşlenir. Örneğin, Teslim toplamı, Teslim API'sinin kaynak olarak kullanıma sunulmasını sağlar.

  • Toplamalar tutarlılık sınırlarıdır. Toplama işlemleri hiçbir zaman tutarsız bir durumda toplama bırakmamalıdır. Bu nedenle, bir istemcinin bir toplamanın iç durumunu işlemesine izin veren API'ler oluşturmaktan kaçınmalısınız. Bunun yerine, toplamaları kaynak olarak kullanıma sunan kaba ayrıntılı API'leri tercih edin.

  • Varlıkların benzersiz kimlikleri vardır. REST'te kaynakların URL biçiminde benzersiz tanımlayıcıları vardır. Bir varlığın etki alanı kimliğine karşılık gelen kaynak URL'leri oluşturun. URL'den etki alanı kimliğine eşleme, istemciye opak olabilir.

  • Bir toplamanın alt varlıklarına kök varlıktan gezinilerek ulaşılabilir. HATEOAS ilkelerini izlerseniz, alt varlıklara üst varlığın temsilindeki bağlantılar aracılığıyla ulaşılabilir.

  • Değer nesneleri sabit olduğundan, güncelleştirmeler değer nesnesinin tamamı değiştirilerek gerçekleştirilir. REST'te, PUT veya PATCH istekleri aracılığıyla güncelleştirmeleri uygulayın.

  • Depo, istemcilerin bir koleksiyondaki nesneleri sorgulamasına, eklemesine veya kaldırmasına olanak tanır ve temel alınan veri deposunun ayrıntılarını soyutlar. REST'te koleksiyon, koleksiyonu sorgulama veya koleksiyona yeni varlıklar ekleme yöntemleriyle ayrı bir kaynak olabilir.

API'lerinizi tasarlarken, yalnızca modelin içindeki verileri değil, aynı zamanda iş işlemlerini ve verilerdeki kısıtlamaları da kullanarak etki alanı modelini nasıl ifade ettiklerini düşünün.

DDD kavramı REST eşdeğeri Örnek
Toplama Kaynak { "1":1234, "status":"pending"... }
Kimlik URL https://delivery-service/deliveries/1
Alt varlıklar Bağlantılar { "href": "/deliveries/1/confirmation" }
Değer nesnelerini güncelleştirme PUT veya PATCH PUT https://delivery-service/deliveries/1/dropoff
Depo Koleksiyon https://delivery-service/deliveries?status=pending

API sürümü oluşturma

API, bir hizmet ile bu hizmetin istemcileri veya tüketicileri arasındaki bir sözleşmedir. Bir API değişirse, ister dış istemciler ister diğer mikro hizmetler olsun, API'ye bağlı istemcilerin bozulması riski vardır. Bu nedenle, yaptığınız API değişikliklerinin sayısını en aza indirmek iyi bir fikirdir. Genellikle, temel alınan uygulamadaki değişiklikler API'de herhangi bir değişiklik gerektirmez. Ancak gerçekçi olarak, bir noktada mevcut API'nin değiştirilmesini gerektiren yeni özellikler veya yeni özellikler eklemek isteyeceksiniz.

Mümkün olduğunda API değişikliklerini geriye dönük olarak uyumlu hale getirin. Örneğin, bir alanı modelden kaldırmaktan kaçının çünkü bu, alanın orada olmasını bekleyen istemcileri bozabilir. İstemcilerin yanıtta anlamadıkları alanları yoksayması gerektiği için alan eklemek uyumluluğu bozmaz. Ancak, hizmetin eski bir istemcinin istekteki yeni alanı atladığı durumu işlemesi gerekir.

API sözleşmenizde sürüm oluşturma desteği. Hataya neden olan bir API değişikliğini eklerseniz yeni bir API sürümü tanıtın. Önceki sürümü desteklemeye devam edin ve istemcilerin hangi sürümü çağıracaklarını seçmesine izin verin. Bunu yapmanın birkaç yolu vardır. Bunlardan biri, her iki sürümü de aynı hizmette kullanıma sunmanızdır. Bir diğer seçenek de hizmetin iki sürümünü yan yana çalıştırmak ve istekleri HTTP yönlendirme kurallarına göre bir veya diğer sürüme yönlendirmektir.

Sürüm oluşturma desteği için iki seçeneği gösteren diyagram.

Diyagram iki bölümden oluşur. "Hizmet iki sürümü destekler", v1 İstemcisi'ni ve v2 İstemcisi'ni tek bir Hizmete işaret eder. "Yan yana dağıtım", v1 İstemcisi'ni bir v1 Hizmetine, v2 İstemcisi ise v2 Hizmetine işaret eder.

Geliştirici süresi, test etme ve işlem yükü açısından birden çok sürümü desteklemenin bir maliyeti vardır. Bu nedenle, eski sürümleri mümkün olan en kısa sürede kullanımdan kaldırabilirsiniz. İç API'ler için API'nin sahibi olan ekip, yeni sürüme geçmelerine yardımcı olmak için diğer ekiplerle birlikte çalışabilir. Bu, ekipler arası idare sürecine sahip olmanın yararlı olduğu durumdur. Dış (genel) API'ler için, özellikle API üçüncü taraflar veya yerel istemci uygulamaları tarafından kullanılıyorsa API sürümünü kullanımdan kaldırmanız daha zor olabilir.

Bir hizmet uygulaması değiştiğinde, değişikliği bir sürümle etiketlemek yararlı olur. Sürüm, hataları giderirken önemli bilgiler sağlar. Kök neden analizinin hizmetin tam olarak hangi sürümünün çağrıldığını bilmesi çok yararlı olabilir. Hizmet sürümleri için anlamsal sürüm oluşturmayı kullanmayı göz önünde bulundurun. Anlamsal sürüm oluşturma bir MAJOR kullanır. KÜÇÜK. PATCH biçimi. Ancak, istemciler yalnızca ana sürüm numarasına göre bir API'yi veya ikincil sürümler arasında önemli (ancak hataya neden olmayan) değişiklikler varsa ikincil sürümü seçmelidir. Başka bir deyişle, istemcilerin bir API'nin sürüm 1 ile sürüm 2 arasında seçim yapıp 2.1.3 sürümünü seçmemesi mantıklıdır. Bu ayrıntı düzeyine izin verirseniz, sürümlerin çoğalmasını desteklemek zorunda olma riskiyle karşılaşıyorsunuz.

API sürümü oluşturma hakkında daha fazla bilgi için bkz. RESTful web API'sinin sürümünü oluşturma.

Bir kez etkili işlemler

bir işlem, ilk çağrıdan sonra ek yan etkiler oluşturmadan birden çok kez çağrılabiliyorsa bir kez etkili olur. Bir yukarı akış hizmetinin bir işlemi birden çok kez güvenli bir şekilde çağırmasına izin verdiğinden, bir kez etkili olma kullanışlı bir dayanıklılık stratejisi olabilir. Bu noktaya ilişkin bir tartışma için bkz . Dağıtılmış işlemler.

HTTP belirtimi GET, PUT ve DELETE yöntemlerinin bir kez etkili olması gerektiğini belirtir. POST yöntemlerinin bir kez etkili olacağı garanti edilmez. POST yöntemi yeni bir kaynak oluşturursa, genellikle bu işlemin bir kez etkili olacağının garantisi yoktur. Belirtim şu şekilde bir kez etkili olduğunu tanımlar:

Bu yöntemle birden çok özdeş isteğin sunucusu üzerindeki amaçlanan etki, bu tür tek bir isteğin etkisiyle aynıysa, istek yöntemi "bir kez etkili" olarak kabul edilir. (RFC 7231)

Yeni bir varlık oluştururken PUT ve POST semantiği arasındaki farkı anlamak önemlidir. Her iki durumda da istemci, istek gövdesinde bir varlığın gösterimini gönderir. Ancak URI'nin anlamı farklıdır.

  • POST yöntemi için URI, koleksiyon gibi yeni varlığın üst kaynağını temsil eder. Örneğin, yeni bir teslim oluşturmak için URI olabilir /api/deliveries. Sunucu varlığı oluşturur ve gibi /api/deliveries/39660yeni bir URI atar. Bu URI yanıtın Konum üst bilgisinde döndürülür. İstemci her istek gönderdiğinde, sunucu yeni bir URI ile yeni bir varlık oluşturur.

  • PUT yöntemi için URI varlığı tanımlar. Bu URI'ye sahip bir varlık zaten varsa, sunucu mevcut varlığı istekteki sürümle değiştirir. Bu URI'ye sahip bir varlık yoksa, sunucu bir varlık oluşturur. Örneğin, istemcinin öğesine bir PUT isteği gönderdiğini api/deliveries/39660varsayalım. Bu URI ile teslim olmadığını varsayarsak, sunucu yeni bir tane oluşturur. Şimdi istemci aynı isteği yeniden gönderirse, sunucu mevcut varlığın yerini alır.

Teslim hizmetinin PUT yöntemini uygulaması aşağıdadır.

[HttpPut("{id}")]
[ProducesResponseType(typeof(Delivery), 201)]
[ProducesResponseType(typeof(void), 204)]
public async Task<IActionResult> Put([FromBody]Delivery delivery, string id)
{
    logger.LogInformation("In Put action with delivery {Id}: {@DeliveryInfo}", id, delivery.ToLogInfo());
    try
    {
        var internalDelivery = delivery.ToInternal();

        // Create the new delivery entity.
        await deliveryRepository.CreateAsync(internalDelivery);

        // Create a delivery status event.
        var deliveryStatusEvent = new DeliveryStatusEvent { DeliveryId = delivery.Id, Stage = DeliveryEventType.Created };
        await deliveryStatusEventRepository.AddAsync(deliveryStatusEvent);

        // Return HTTP 201 (Created)
        return CreatedAtRoute("GetDelivery", new { id= delivery.Id }, delivery);
    }
    catch (DuplicateResourceException)
    {
        // This method is mainly used to create deliveries. If the delivery already exists then update it.
        logger.LogInformation("Updating resource with delivery id: {DeliveryId}", id);

        var internalDelivery = delivery.ToInternal();
        await deliveryRepository.UpdateAsync(id, internalDelivery);

        // Return HTTP 204 (No Content)
        return NoContent();
    }
}

İsteklerin çoğunun yeni bir varlık oluşturması beklenir, bu nedenle yöntem iyimser bir şekilde depo nesnesini çağırır CreateAsync ve bunun yerine kaynağı güncelleştirerek yinelenen kaynak özel durumlarını işler.

Sonraki adımlar

İstemci uygulamaları ve mikro hizmetler arasındaki sınırda API ağ geçidi kullanma hakkında bilgi edinin.