Hizmetler arasındaki tüm veri alışverişi iletiler veya API çağrıları aracılığıyla gerçekleştiğinden, mikro hizmet mimarisinde iyi API tasarımı önemlidir. Sohbetli G/Ç oluşturmamak için API'lerin verimli olması gerekir. Hizmetler bağımsız olarak ç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.
İki API türü 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ı dikkate almanız gerekir. Hizmetlerinizin ayrıntı düzeyine bağlı olarak, hizmetler arası iletişim çok fazla ağ trafiğine neden olabilir. Hizmetler hızla G/Ç sınırına bağlanabilir. Bu nedenle serileştirme hızı ve yük boyutu gibi önemli noktalar daha önemli hale gelir. HTTP üzerinden REST 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 gereken noktalar
Api'nin nasıl uygulaneceğini seçerken göz atın.
REST ve RPC karşılaştırması. REST stili arabirim ile RPC stili bir arabirim arasındaki dengeleri göz önünde bulundurun.
REST, etki alanı modelinizi ifade etmenin doğal bir yolu olabilecek kaynakları modeller. 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ış semantiği vardır. 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 neden olabilir. 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 seçeneğidir. 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). IDL, BIR API'nin yöntemlerini, parametrelerini ve dönüş değerlerini tanımlamak için kullanılır. idl istemci kodu, serileştirme kodu ve API belgeleri oluşturmak için kullanılabilir. IDL'ler 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 olarak OpenAPI (eski adıyla Swagger) tercih edilir. Ayrıca, resmi bir tanım dili kullanmadan bir HTTP REST API oluşturabilirsiniz, ancak ardından kod oluşturma ve test etme avantajlarını kaybedersiniz.
Serileştirme. Nesneler kablo üzerinden 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'un birlikte çalışabilirlik açısından avantajları vardır. 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'ya destek.
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 bir istemci saplamasına ihtiyacı olmadığından çok az bağlantı 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ı istemcileri ile 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 yeterince iyi performans gösterip göstermediğini doğrulamak için geliştirme sürecinin erken aşamaları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, etki alanını modellemelidir. Bu hizmetler arasındaki bir sözleşmedir ve ideal olarak yalnızca yeni işlevler eklendiğinde değişmelidir, yalnızca bir kodu yeniden düzenlediğiniz veya bir veritabanı tablosunu normalleştirdiğiniz için değil.
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ığı artırabilir. Hizmetler arası iletişim makalesi bu sorunu daha ayrıntılı olarak ele alır.
HTTP yöntemleri zaman uyumsuz semantiğine sahip olabilir; burada yöntem hemen bir yanıt döndürür, ancak hizmet işlemi zaman uyumsuz olarak yürütür. Bu durumda yöntemin, isteğin işleme için kabul edildiğine 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 İstek-Yanıt 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 getirmek için tasarlanmıştır. DDD'nin birçok tartışmasında, desenler oluşturucular veya özellik alıcıları 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ılmanız gerekir:
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;
}
}
Bu tür kodlama uygulamaları, geleneksel bir monolitik uygulama oluştururken ö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:
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 bir HTTP yanıtında bir JSON nesnesi) geri alır.
Bu örnek, uçak ve havacılık sektörleri için idealdir.
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 monolitteki kodla karşılaştırıldığında daha küçük bir kullanıma 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:
Toplamalar REST'teki kaynaklarla doğal olarak eşlenir. Örneğin, Teslim toplaması, 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çınmanız gerekir. Bunun yerine, toplamaları kaynak olarak kullanıma sunan kaba ayrıntılı API'leri tercih edin.
Varlıkların benzersiz kimlikleri vardır. REST'de 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 gösterimindeki bağlantılar aracılığıyla erişilebilir.
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 temel alınan veri deposunun ayrıntılarını soyutlayarak bir koleksiyondaki nesneleri sorgulamasına, eklemesine veya kaldırmasına olanak tanır. 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 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 hizmetle istemcileri veya bu hizmetin tüketicileri arasındaki bir sözleşmedir. Bir API değişirse, bunlar dış istemciler veya diğer mikro hizmetler olsun, API'ye bağlı olan 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 IÇIN API'de herhangi bir değişiklik yapılması gerekmez. Ancak gerçekçi olmak gerekirse, 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, alanın orada olmasını bekleyen istemcileri bozabileceğinden, bir modelin alanını kaldırmaktan kaçının. İstemcilerin yanıtta anlamadıkları alanları yoksayması gerektiğinden, 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 tanıtırsanız 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.
Diyagram iki bölümden oluşur. "Hizmet iki sürümü destekler", v1 İstemcisi ve v2 İstemcisi'ni tek bir Hizmeti işaret eder. "Yan yana dağıtım", v1 İstemcisi bir v1 Hizmetine işaret eder ve v2 İstemcisi bir v2 Hizmetine işaret eder.
Geliştirici süresi, test ve operasyonel ek 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ırma iyi olur. İç 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, istemcilerin 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çmesi gerekir. Başka bir deyişle, istemcilerin bir API'nin 1. ve 2. sürümü arasında seçim yapmak mantıklıdır, ancak sürüm 2.1.3'ün seçilmesi mantıklı değildir. 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
İlk çağrıdan sonra ek yan etkiler üretmeden birden çok kez çağrılabiliyorsa bir işlem bir kez etkili olur. Bir yukarı akış hizmetinin bir işlemi birden çok kez güvenli bir şekilde çağırmasına izin verdiğinden, aynı anda 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 olduğu garanti edilmez. Belirtim şu şekilde bir kez etkili olduğunu tanımlar:
Bu yöntemle birden çok özdeş isteğin sunucusu üzerindeki amaçlanan etki 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/39660
yeni 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 var olan 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/39660
varsayalı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.
Burada, Teslim hizmetinin PUT yöntemini uygulaması yer alı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();
}
}
çoğu isteğin yeni bir varlık oluşturması beklenir; bu nedenle yöntemi, depo nesnesini iyimser bir şekilde ç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.