Veri dönüşümü: monolitik uygulamalardan mikro hizmetlere düşük riskli evrim

Takip etmek için tıklayın InfoQ , Yapışkan resmi hesap

Programcının saat 8'de teknik kahvaltısını alın

Yazar | Christian Posta

Çevirmen | Hai Song

Kaynak | EAWorld

İlk bölümde, sistem erişimini ve iş değerini etkilemeden mikro hizmetlerin mimariye nasıl dahil edileceğini tanıtmak için belirli bir örnek kullandık. İkinci bölümde, yukarıda bahsedilen mimari stratejiler ve hedefler ile uyumlu bazı teknolojileri listeledik. Bu makalede, monolitik mimariyle (en azından ilk aşamada) verileri paylaşması gerekebilecek yeni hizmetlerin nasıl ekleneceğine odaklanarak çözümün ikinci bölümüne devam edeceğiz ve ardından bazı daha karmaşık dağıtım senaryolarını tanıtacağız. Ayrıca, kullanıcı sözleşmesi testi için Arquilli-Analgeron'un nasıl kullanılacağını ve hizmet mimarimizdeki API değişikliklerinin üstesinden gelmek için nasıl kullanılacağını da keşfedeceğiz. Twitter'da beni takip edin: @christianposta veya çoğu içerik için adresini ziyaret edin.

Bu makalenin birinci ve ikinci bölümlerini incelemek için bağlantıya da tıklayabilirsiniz.

1 Teknoloji

Bu konunun ikinci, üçüncü ve dördüncü bölümlerinde yer alan teknolojiler aşağıdaki gibidir: Bu teknolojilerin uygulamamızda belirli bir yol gösterici rolü olacaktır:

  • Geliştirici hizmet çerçevesi (Spring Boot, WildFly, WildFly Swarm)

  • API tasarımı (APICur.io)

  • Veri çerçevesi (Spring Boot Teiid, Debezium.io)

  • Entegrasyon aracı (Apache Camel)

  • Istio Hizmet Ağı

  • Veritabanı taşıma aracı (Liquibase)

  • Karanlık başlatma / özellik bayrağı çerçevesi (FF4J)

  • Dağıtım / CI-CD platformu (Kubernetes / OpenShift)

  • Kubernetes geliştirme araçları (Fabric8.io)

  • Test araçları (Arquillian, Pact / Arquillian Algeron, Hoverfly, Spring-Boot Test, RestAssured, Arquillian Cube)

Birlikte pratik yapmak istiyorsanız, adresindeki TicketMonster eğitimini benimle birlikte örnek bir proje olarak kullanabilirsiniz. Monolitik uygulamalardan mikro hizmetlere geçişin nasıl tamamlanacağını göstermek için bu öğreticiyi ödünç aldım. İlgili kodu ve dokümantasyonu github'da da bulabilirsiniz (dokümantasyon halen hazırlanmaktadır): https://github.com/ticket-monster-msa/monolith

İkinci bölümde monolitik uygulamadan ayrılacak bir mikro hizmet (Siparişler / Rezervasyon) eklemeye başlıyoruz. Bu adımı başlatmak için uygun API tasarımını simüle etmek ve keşfetmek için Hoverfly kullanıyoruz.

2 API'yi uygulama ile yerleştirme

Dikkat edilecek noktaları gözden geçirin

  • Tanım açısından, çıkarılan veya yeni oluşturulan hizmetin veri modeli, tek uygulamanın veri modeli ile sıkı bir şekilde birleştirilir.

  • Monolitik uygulama, uygun düzeyde veri elde etmek için bir API sağlamayabilir

  • Verileri alsak bile, veri dönüşümü gerçekleştirmek için çok sayıda kod örneğine ihtiyacımız var

  • Veriler üzerinde salt okunur sorgular gerçekleştirmek için arka uç veritabanına geçici olarak doğrudan erişebiliriz Monolithic uygulamalar nadiren veritabanlarını değiştirir

İlk bölümde, monolitik uygulama veritabanına doğrudan bağlanan bir çözümden bahsedildi. Bu örnekte böyle bir çözümü benimsememiz gerekiyor çünkü veri tabanındaki veriler yeni Orders servisi tarafından kullanılacak ve bu yeni servisi monolitik uygulamadan da ayırmamız gerekiyor. Ayrıca, bu yeni hizmeti kullanıma sunduktan sonra tek bir uygulamada trafik yükleyebileceğini ve içerikle tutarlı bir görünüme sahip olacağını umuyoruz; örneğin, bir süre içinde iki hizmeti aynı anda çalıştıracağız. Bu işlemin doğrudan ayrıştırma eyleminin özüne çarpacağını unutmayın: Yeni mikro hizmeti sihirli bir şekilde çağırmamız imkansızdır, böylece mevcut yükü etkilemeden tüm rezervasyon veya sipariş mantığını doğru bir şekilde kapsayabilir.Bu gerçekçi değildir. nın-nin.

Yani tek bir uygulamanın veritabanına bağlanmak istemiyorsak, başka hangi seçenekler var? Bazılarını sıralayabilirim ... Elbette, başka önerileriniz varsa, lütfen yorum yapmaktan veya bana tweet atmaktan çekinmeyin:

  • Monolitik uygulamalar tarafından açığa çıkan mevcut API'leri kullanın

  • Tek bir uygulamanın veritabanına erişmek için özel olarak yeni bir API oluşturun; veriye ihtiyacımız olduğunda onu istediğimiz zaman arayabiliriz

  • Tek bir uygulamadan yeni bir mikro hizmete, verilere sahip olmak için bir ayıklama dönüştürme yükü (ETL) yapın

Mevcut API'yi kullanın

Bunu yaparsanız, kullanım hakkında derinlemesine düşünmelisiniz. Normal koşullar altında, mevcut API'ler oldukça kabadır ve düşük seviyeli kullanım için uygun olamaz ve bunları yeni hizmette veri modeline uyarlamak için çok sayıda ayarlamaya ihtiyaç duyulabilir. Bu yeni Siparişler hizmetinde, yeni hizmete yapılan her giriş çağrısı için, eski API veya tek uygulama API'sini sorgulamanız (burada birden çok uç nokta olabilir) ve ardından yanıt değerini kendi tercihlerinize göre işlemeniz gerekir. Kısayol kullanmak istemediğiniz sürece bunda doğal olarak yanlış bir şey yoktur, ancak kısayolların kullanılması yeni hizmetin veri modelini tek uygulamadan, eski API'den veya veri modelinden ciddi şekilde etkileyecektir. Örneğimde, iki veri modeli ilk başta benzer olsa da, sadece standartlaştırılmış bir veri modeli elde etmekle kalmayıp, hızlıca yinelemek ve doğru etki alanı modelini (etki alanı modeli) elde etmek için DDD'yi kullanmak istiyoruz.

Yeni bir düşük seviyeli API oluşturun

Mevcut monolitik uygulamanın bir API'si yoksa veya API ayrıntı düzeyi çok kabaysa ya da kullanmaya devam etmek istemiyorsanız, monolitik uygulamanın veritabanına doğrudan bağlanmak ve yeni Siparişleri kullanmak için yeni bir düşük düzeyli API oluşturabilirsiniz. Verileri ifşa etmek için gereken hizmet düzeyi. Bu aynı zamanda kabul edilebilir bir çözümdür. Öte yandan, benim deneyimim, yeni Siparişler hizmetinin bu düşük seviyeli arayüze çok sayıda sorgu veya API çağrısı yazmayacağı, ancak önceki uygulamaya benzer şekilde bellek bağlantısındaki yanıt değerini çalıştıracağı yönündedir. Bu, bir veritabanı yürütmek gibidir. Yine, özünde, bunda yanlış bir şey yoktur, ancak Siparişler hizmeti için çok fazla fazladan kod yazmayı gerektirir (yalnızca birkaç farkla çok sayıda tekrar) ve bu genellikle geçici, geçici bir çözümdür.

Tek bir uygulamadan yeni bir hizmete, bir çıkarma dönüştürme yükü (ETL) yapın

Bir dereceye kadar, bunu gerçekten yapmamız gerekebilir. Ancak yeni bir hizmetin etki alanı modelini incelerken eski tek uygulamayla uğraşmak istemeyebiliriz. Ayrıca, her ikisi de trafiği yükleyebilen yeni hizmet ve tek uygulamanın aynı anda çalışmasını sağlamak istiyoruz. ETL yöntemi benimsenirse, Siparişler hizmetinin durumunu korumanın bir yolunu bulmamız gerekir çünkü bu içerikler zamanında senkronize edilemeyebilir. Bu, sonunda büyük bir sorun haline gelecektir.

Yeni Sipariş hizmetinin bir geliştiricisi olarak, yeni hizmetin sorunu alan modeli perspektifinden ele almasının mantıklı olduğunu düşünüyorum (not: Veri modelinden bahsetmiyorum, ikisi arasında bir fark var). Alan modelini etkileyebileceği için dış uygulamanın etkisi mümkün olduğunca ortadan kaldırılmalıdır. Aradaki fark, veri modelinin sistemdeki statik verilerin nasıl ilişkili olduğunu göstermesidir, bu da kalıcılık katmanında verilerin nasıl depolanacağına ilişkin bir temel sağlayabilir. Etki alanı modeli, etki alanının analitik alanının davranışını tanımlamak için kullanılır ve daha çok kullanım durumlarına veya işlem davranışına odaklanma eğilimindedir. Örneğin, sorunu tanımlamak için kullandığımız kavram veya model bir alan modelidir. DDD ustası Vaughn Vernon, bu ayrımı daha ayrıntılı olarak tartışan bir dizi makale yazdı.

Çözümüm, ideal etki alanı modeline veri işleme modelleri eklemek için gereksiz kodu azaltmaya veya hatta ortadan kaldırmaya yardımcı olabilecek, Ticket Monster Orders içinde açık kaynaklı bir Teiid projesi sunmaktır. Teiid, her zaman farklı veri kaynaklarını (ilişkisel veritabanları, ilişkisel olmayan veritabanları, formatlanmamış dosyalar vb.) Elde edebilen ve bunları tek bir sanallaştırılmış görünüm olarak sunan bir veri sendikasyon yazılımı olmuştur. Genellikle, veri analistleri, raporlama amacıyla verileri toplamak için Teiid'i kullanır. Ancak, geliştiricilerin yukarıdaki sorunları çözmek için onu nasıl kullanabilecekleriyle daha çok ilgileniyoruz. Neyse ki, Teiid topluluğundan insanlar, özellikle Ramesh Reddy, sorunları çözme sürecinde üretilen gereksiz kodların ortadan kaldırılmasına yardımcı olmak için Teiid ve Spring Boot için bazı güzel uzantılar oluşturdu.

Teiid Spring Boot'a Giriş

Tekrarlamak gerekirse: Hizmetin etki alanı modeline odaklanmalıyız, ancak başlangıçta etki alanı modelini destekleyen veriler monolitik uygulamada veya arka uç veritabanında hala var olacaktır. Tek bir uygulamanın veri modeli yapısını istenilen alan modeli ile birleştirip veri entegrasyonu ile ilgili fazlalık kodları kaldırabilir miyiz?

Teiid Spring Boot, etki alanı modeline odaklanmamızı ve model için ek açıklamalar oluşturmak için JPA @entity'yi kullanmamızı sağlar. Bu, diğer modellerle aynıdır. Aynı zamanda, modeli yeni veritabanımızla eşleyebilir ve monolitik mimariyi sanal olarak eşleyebilir. veri tabanı. Teiid-spring-boot kullanmaya başlamak için yalnızca aşağıdaki bağımlılıkları içe aktarmanız gerekir:

< bağımlılık > < Grup kimliği > org.teiid.spring < /Grup kimliği > < artifactId > teiid-spring-boot-starter < / artifactId > < versiyon > 1.0.0-SNAPSHOT < / version > < /bağımlılık >

Bu, Spring'in otomatik yapılandırmasına bağlanacak ve sanal veritabanımızı kurmaya çalışan bir başlangıç projesidir (tek uygulamanın veritabanı ve bu hizmete ait gerçek fiziksel veritabanı tarafından desteklenir).

Daha sonra, her arka uç için Spring Boot'ta veri kaynağını tanımlamamız gerekiyor. Bu örnekte, iki MySQL veritabanı kullandım, ancak bu sadece bir detay. İki özdeş veri kaynağı ile sınırlı değiliz ve ilişkisel veritabanı yönetim sistemleriyle (RDBM'ler) sınırlı tutulmamalıyız. Aşağıdakiler örneklerdir:

spring.datasource.legacyDS.url = jdbc: mysql: // localhost: 3306 / ticketmonster? useSSL = false spring.datasource.legacyDS.username = ticket spring.datasource.legacyDS.password = monster spring.datasource.legacyDS.driverClassName = com.mysql.jdbc.Driver spring.datasource.ordersDS.url = jdbc: mysql: // localhost: 3306 / siparişler? useSSL = false spring.datasource.ordersDS.username = ticket spring.datasource.ordersDS.password = monster spring.datasource.ordersDS.driverClassName = com.mysql.jdbc.Driver

Etki alanı modelimizi taramak ve sanal olarak tek bir uygulamayla eşlemek için teiid-spring -boot'u yapılandırmaya başlayalım. Uygulama özelliklerinde aşağıdaki içeriği ekliyoruz:

spring.teiid.model.package = org.ticketmonster.orders.domain

Teiid Spring Boot, eşlemeyi @entity tanımında bir açıklama olarak belirlememizi sağlar. Aşağıda bir örnek verilmiştir (github'daki etki alanı nesnelerinin tam uygulamasına ve tam uygulamasına bakın):

@SelectQuery ("SEÇIN s.id, s.description, s.name, s.numberOfRows AS number_of_rows, s.rowCapacity AS row_capacity, Mekan_id, v.name AS Mekan_name ESASLARDAN ESKİDEN ESKİDS.VENE v S.venue_id ÜZERİNDE = v.id; ") @Entity @Table (ad =" bölüm ", benzersizConstraints = @ UniqueConstraint (columnNames = {"name", "mekan_id"})) public class Bölüm, Serializable {@Id @GeneratedValue (strateji = IDENTITY) özel Uzun id; @NotEmpty özel Dize adı; @NotEmpty özel Dize açıklaması; @Not @Embedded özel MekanId mekanId; @Column (name = "number_of_rows") private int numberOfRows; @Column (name = "row_capacity") private int rowCapacity;

Yukarıdaki örnekte, eski veri kaynağı (legacyDS. *) Ve alan modeli arasındaki eşlemeyi tanımlamak için @SelectQuery kullanıyoruz. Model için doğru verileri elde etmek için genellikle bu eşlemelerin çok sayıda JOIN işlemine sahip olabileceği unutulmamalıdır; bu nedenle, bir REST API ek açıklamasına yalnızca bir JOIN yazmak en iyisidir, çünkü ek açıklama çok sayıda veri dönüşümü yazmaya çalışacaktır. Gereksiz kod (yalnızca sorgu değil, aynı zamanda beklenen etki alanı modelimizle gerçek eşleme). Yukarıdaki durumda, monolitik uygulamanın veritabanından alan modeline yalnızca eşlememiz gerekir, ancak birleştirme işlemini kendi veritabanımızda gerçekleştirmek istiyorsak ne olur? Bunu yapabilirsiniz (tam uygulama için ticket.java'ya bakın):

@SelectQuery ("SELECT id, CAST (price AS double), number, rowNumber AS row_number, section_id, ticketCategory_id AS ticket_category_id, tickets_id AS booking_id AS legacyDS.Ticket" + "UNION ALL SELECT id, fiyat, numara, satır_numarası, section_id, ticket_category_id, booking_id FROM ordersDS.ticket ")

Burada, monolitik uygulama veri tabanının ve yerel Siparişler veri tabanının iki görünümünü birleştirmek için UNION ALL anahtar kelimesini kullandığımızı lütfen unutmayın.

Yükseltme ve Ekleme sorunları ne olacak?

Örneğin, Siparişler hizmetimiz Siparişleri veya rezervasyonları saklamalıdır. @ InsertQuery ek açıklamasını tüm rezervasyon DDD'sine şu şekilde ekleyebilirsiniz:

@InsertQuery ("HER SATIR İÇİN \ n" + "ATOMİK BAŞLATIN \ n" + "siparişlere EKLEYİNDS.booking (id, performance_id, performance_name, cancellation_code, created_on, contact_email) değerler (NEW.id, NEW.performance_id, NEW.performance_name, NEW.cancellation_code, NEW.created_on, NEW.contact_email); \ n "+" END ")

Teiid-spring-boot notlarının geri kalanı için lütfen belgelere bakın.

Yeni bir rezervasyon rezerve ettiğimizde (JPA, bahar verileri vb.), Sanal veritabanının bunu kendi Siparişler veritabanında saklamayı bildiği görülebilir. Spring Data'yı kullanmayı tercih ederseniz, yine de teiid-spring -boot'tan tam olarak yararlanabilirsiniz. İşte başka bir teiid-spring-boot örneği:

genel arayüz CustomerRepository CrudRepository'yi genişletir < Müşteri, Uzun > {@Query ("c.ssn =: ssn'de Müşteri c'den c'yi seçin") Akış < Müşteri > findBySSNReturnStream (@Param ("ssn") String ssn);}

Uygun bir teiid-spring-boot haritalama ek açıklaması seçersek, bu yay veri havuzu sanal veritabanı katmanını doğru bir şekilde anlayabilir ve etki alanı modelini beklendiği gibi işleyebilir.

Yine: Bu, nihai çözümden ziyade mikro hizmet ayrıştırmasının ilk adımlarında geçici bir çözümdür. Hala devam eden örnekte yinelememiz gerekiyor. Eşleme veya çeviri yaparken oluşabilecek ortak kod ve sorunları azaltmaya çalışıyoruz.

Benzer şekilde, monolitik bir uygulama veritabanındaki düşük seviyeli verilere erişmek için hala basit bir API oluşturmayı düşünüyorsanız, o zaman teiid-spring -boot sizin için yine de yararlı olacaktır. Teiid-spring -boot tarafından oluşturulan odata entegrasyonunu kullanmayan bu tür bir API'yi çok hızlı bir şekilde yayınlayabilirsiniz. Daha fazla içerik için odata modülüne göz atın (bu projenin belgelerini hala yazdığımızı unutmayın)

Ayrıştırmanın bu düğümünde, uygun API, etki alanı modeli ile işbirliği yapan ve kendi veritabanımıza bağlı olan ve veritabanının etki alanı modelinde kullanılabilmesi için geçici olarak tek veritabanımıza sanal bir eşleme oluşturan bir Siparişler hizmet uygulaması olmalıdır. . Daha sonra, onu üretime ve gri tonlamalı olarak çevrim içi hale getirmemiz gerekiyor.

3 Yeni mikro hizmete gölge trafiği gönderin (karanlık başlatma)

Dikkat edilecek noktaları gözden geçirin

  • Kod yoluna yeni sipariş hizmetlerinin dahil edilmesi risklidir

  • Yeni hizmetlere kontrollü bir şekilde trafik göndermek için

  • Trafiğin yeni hizmetlere ve eski kod yollarına yönlendirilebileceğini umuyoruz

  • Yeni hizmetlerin etkisini ölçmek ve izlemek için

  • Daha sorunlu iş tutarlılığı sorunlarını önlemek için "sentetik" şeyleri işaretlemeye çalışın

  • Yeni özellikleri belirli gruplara veya kullanıcılara dağıtmak istiyorsanız

Bu konunun ilk bölümünde bahsettiğimizi takiben, monolitik uygulamayı yeni Siparişler hizmetini çağıracak şekilde değiştireceğiz. Bu, yeni hizmetleri çağırmak için monolitik uygulamadaki mevcut mantığı dönüştürmek veya genişletmek için Michael Feather'ın kitabındaki bazı teknikleri kullanacaktır. Örneğin, monolitik uygulamamız createBookings'i uygularken şöyle görünür:

@POST @Consumes (MediaType.APPLICATION_JSON) genel Yanıt createBooking (BookingRequest bookingRequest) {deneyin {// bu istekteki bilet fiyat kategorilerini belirleyin Ayarla < Uzun > priceCategoryIds = bookingRequest. getUniquePriceCategoryIds; // bu rezervasyonun ilişkilerini oluşturan varlıkları yükle Performans performansı = getEntityManager. find (Performance.class, bookingRequest.getPerformance); // Bir rezervasyonda çeşitli bilet türlerine sahip olabileceğimiz için, ilgili olanların hepsini yüklememiz gerekiyor, // id Map < Uzun, Bilet Fiyatı > ticketPricesById = loadTicketPrices (priceCategoryIds); // Şimdi, yayınlanan verilerden rezervasyonu oluşturmaya başlayın // Önce basit şeyleri ayarlayın! Booking booking = new Booking; booking.setContactEmail (bookingRequest.getEmail); booking.setPerformance (performance); rezervasyon. setCancellationCode ("abc"); // Şimdi, talep edilen her bilet üzerinde yineliyoruz, ve bunları bölüm ve kategoriye göre düzenleyin // ait olan bilet isteklerini tahsis etmek istiyoruz aynı bölüm bitişik Harita < Kesit, Harita < TicketCategory, TicketRequest > > ticketRequestsPerSection = yeni Ağaç Haritası < Bölüm, java.util.Map < TicketCategory, TicketRequest > > (SectionComparator.instance); for (TicketRequest ticketRequest: bookingRequest.getTicketRequests) {final TicketPrice ticketPrice = ticketPricesById.get (ticketRequest.getTicketPrice); if (! ticketRequestsPerSection.containsKey (ticketPrice.getSection)) {ticketRequestsPerSection .put (ticketPrice.getSection, yeni HashMap < TicketCategory, TicketRequest > );} ticketRequestsPerSection.get (ticketPrice.getSection) .put (ticketPricesById.get (ticketRequest. getTicketPrice) .getTicketCategory, ticketRequest);}

Tıpkı diğer monolitik uygulamalar gibi, burada kodun sadece küçük bir kısmı gösterilir ve listelenmeyen daha fazlası vardır.İçerikleri uzun ve karmaşıktır ve tam olarak anlaşılması kolay değildir. Bu yüzden onları şuna çevireceğiz:

@POST @Consumes (MediaType.APPLICATION_JSON) public Response createBooking (BookingRequest bookingRequest) {Response response =; if (ff.check ("orders-internal")) {response = createBookingInternal (bookingRequest);} if (ff.check (" siparişler-hizmet ")) {if (ff.check (" siparişler-dahili ")) {createSyntheticBookingOrdersService (bookingRequest);} else {response = createBookingOrdersService (bookingRequest);}} dönüş yanıtı;}

Dönüştürülen kod daha az içerik, daha organize ve çalıştırılması daha kolaydır. Peki ne oldu? Ff.check (...) nedir?

Burada izlenecek önemli bir nokta, tek bir uygulamada ne kadar az değişiklik olursa o kadar iyidir; ideal olarak, bu değişikliklerin diğer içerik üzerinde olumsuz bir etkisi olup olmadığını doğrulamaya yardımcı olmak için birim, bileşen, entegrasyon veya sistem testi yapmak istiyoruz. Bu mümkün değilse, test edilmesini sağlamak için stratejik olarak yeniden düzenleme yapmamız gerekir.

Değiştirilen kısımda, mevcut çağrı akışı en iyisi olduğu gibi bırakılır: bu yüzden önceki uygulamayı createBookingInternal adlı bir yönteme taşıyoruz ve olduğu gibi bırakıyoruz. Ancak, Siparişler hizmetinin yeni kod yolunu çağırmak için yeni bir yöntem de kullandık. Ve aşağıdaki işlevleri gerçekleştirebilen bir özellik bayrağı kitaplığını etkinleştirir:

  • Siparişleri gerçekleştirmek için tam zamanlı çalıştırma / konfigürasyon kontrolü

  • Yeni özellikleri devre dışı bırakın

  • Hem yeni hem de eski özellikleri etkinleştirin

  • Tamamen yeni özelliklere geçin

  • Kaldır anahtarı tüm işlevi

Özellik Bayrakları 4 Java (FF4j) burada kullanılır ve elbette Launch Darkly gibi yönetilen SaaS sağlayıcıları dahil olmak üzere başka programlama dili alternatifleri de vardır. Elbette, çerçeveyi kendiniz de yazmayı seçebilirsiniz, ancak bu mevcut proje işlevleri kolayca kullanılabilir ve doğrudan kullanılabilir. Bu, Facebook'un (ve diğerlerinin) kontrol çerçevesine çok benzer. Dağıtım ve sürüm arasındaki farkı incelemek için lütfen buraya bakın.

FF4j'yi kullanmak için, bağımlılıkların pom.xml'ye eklenmesi gerekir

< bağımlılık > < Grup kimliği > org.ff4j < /Grup kimliği > < artifactId > ff4j çekirdekli < / artifactId > < versiyon > $ {ffj4.version} < / version > < /bağımlılık >

Daha sonra ff4j.xml dosyasındaki özellikleri detaylandırabilir, birleştirebiliriz, vb. Karmaşık özellikler veya özellik gruplaması hakkında daha ayrıntılı bilgi için lütfen ff4j belgelerine bakın:

< özellikleri > < özellik uid = "siparişler-dahili" etkinleştir = "doğru" description = "Eski sipariş uygulamasına devam et" / > < özellik uid = "siparişler-hizmet" etkinleştir = "yanlış" description = "Yeni sipariş mikro hizmetini çağır" / > < /özellikleri >

Ardından, bir FF4j nesnesini başlatabilir ve bu özelliklerin kodda etkinleştirilip etkinleştirilmediğini test etmek için kullanabiliriz:

FF4j ff4j = new FF4j ("ff4j.xml"); if (ff4j.check ("özel-özellik")) {doSpecialFeature;}

"Kutudan çıkar çıkmaz" uygulama, özellikleri belirtmek için ff4j.xml yapılandırma dosyasını kullanır. Daha sonra, çalışma zamanında özellikleri değiştirebilirsiniz (aşağıya bakın), ancak bir sonraki adıma geçmeden önce, bu özelliklerin ve bunların etkin veya devre dışı gibi ilgili durumlarının önemli (non- yedekleme için kalıcı depo (kalıcı depo) cihazının önemsiz) dağıtımı. Lütfen ff4j sitesindeki özellik deposu belgelerine bakın.

Çalışma zamanında, özelliğin çalışma zamanında durumunu da yapılandırabilmek veya değiştirebilmek istiyoruz. FF4j, uygulamadaki özelliklerin durumunu görüntülemek veya değiştirmek için dağıtım için kullanılabilecek bir web konsoluna sahiptir:

Varsayılan olarak, dağıtım için yalnızca eski özellikleri etkinleştireceğiz. Başka bir deyişle, varsayılan olarak kod yürütme yolu ve hizmet performansı değişmemiştir. Daha sonra, canary dağıtabilir ve hem eski kod yolunu hem de yeni yolu etkinleştirmek için özellik bayraklarını kullanabiliriz ve yeni yol, yeni Siparişler hizmetini çağırır. Bazı hizmetler için çok fazla dikkat etmemize gerek olmayabilir, sadece ikinci kod yolunu etkinleştirin. Ancak, durumu değiştiren olaylardan kaçınmak için bunun "test" veya "sentetik" bir işlem olduğunu hatırlatmamız gerekir. Burada eski kod yolu ve yeni kod yolu aynı anda etkinleştirildiğinde, Siparişler hizmetine gönderilen mesajı "sentetik" olarak işaretleyeceğiz. Bu, Siparişler hizmetine normal bir istek olarak işlenmesi gerektiğini ancak ardından işleme sonucunun atılması veya geri alınması gerektiğini hatırlatır. Bu, yeni kod yolunun ne yaptığını anlamak ve ilgili sonuçları, olumsuz etkileri, geri bildirim süresi veya gecikme etkileri açısından eski yolla karşılaştırmak için çok değerlidir. Yalnızca yeni kod yolu etkinleştirilirse ve eski kod yolu devre dışı bırakılırsa, sentetik göstergeler veya işaretler olmadan yalnızca gerçek zamanlı istekleri göndeririz.

4 Belirlenmiş hizmet sözleşmesi

Şu anda, muhtemelen monolitik uygulamayı rezervasyon ve sipariş süreci için yeni Siparişler hizmetine bağlamalıyız. Şimdi monolitik bir uygulama için, Siparişler hizmetini ararken sözleşmesini veya veri gereksinimlerini netleştirmek için iyi bir zaman. Elbette, Siparişler hizmeti, bazı belirli işlevleri veya SLA'ları, SLO'ları vb. Sağlamayı vaat eden bağımsız ve özerk bir hizmettir, ancak dağıtılmış bir sistem oluşturmaya başladığımızda, hizmet etkileşimleriyle ilgili varsayımları anlamak ve bunları netleştirmek gerekir.

Genellikle, soruna tedarikçinin bakış açısından bakarız. Bu makale durumunda, kullanıcının bakış açısından ilerliyoruz. Servis sağlayıcının bakış açısından, kullanıcılar gerçekte neyi kullanıyor veya değer veriyor? Sağlayıcılara, sağlanan hizmetlerin kullanımını ve hizmetler değiştiğinde dikkat edilmesi gerekenleri anlamaları için bu tür bir geri bildirim sağlayabilir miyiz, örneğin mevcut uyumluluğu bozmak istemiyoruz. Kullanıcı odaklı sözleşmeler fikrini kullanmak ve açık varsayımlar yapmak istiyoruz. Hizmetler arasındaki sözleşmeleri belirtmek için (kullanıcı odaklı sözleşmelere odaklanın) programlama dillerini göz ardı eden bir belge biçimi olan Pact adlı bir proje kullanacağız. Bildiğim kadarıyla, DiUS adlı Avustralyalı bir teknoloji şirketi Pact projesini kısa süre önce başlattı.

Yukarıdaki resim Pakt belgesindendir

Bir arka uç hizmeti örneğine bakalım. Arka uç-v2 uygulaması için, hizmet sağlayıcının (Siparişler hizmeti) beklentilerini özetleyen bir kullanıcı sözleşmesi kuralı oluşturacağız. / Rest / bookings'e bir POST HTTP isteği gönderdiğimizde, beklentilerimizi aşağıdaki şekillerde vurgulayabiliriz.

@Pact (provider = "orders_service", tüketici = "test_synthetic_order") genel RequestResponsePact createFragment (PactDslWithProvider oluşturucu) {RequestResponsePact pact = builder .given ("mevcut gösteriler") .uponReceiving ("rezervasyon isteği") .path ("/ rest / bookings ") .matchHeader (" Content-Type "," application / json ") .method (" POST ") .body (bookingRequestBody) .willRespondWith .body (sentetikBookingResponseBody) .status (200) .toPact; dönüş pakt;}

Sağlayıcı tarafından sağlanan hizmet arandığında ve belirli bir konuya aktarıldığında, bir HTTP 200 ve sözleşmeyle eşleşen bir yanıt değeri olacaktır. Hadi bir bakalım. Öncelikle, rezervasyon talebi gövdesinin nasıl belirtileceğine bir göz atalım:

private DslPart bookingRequestBody {PactDslJsonBody body = new PactDslJsonBody; body .integerType ("performance", 1) .booleanType ("sentetik", true) .stringType ("email", "foo@bar.com") .minArrayLike ("ticketRequests" , 1) .integerType ("ticketPrice", 1) .integerType ("miktar") .closeObject .closeArray; dönüş gövdesi;}

Pact-jvm, pact-JVM-JUnit modülünü en aşina olduğumuz test çerçevesine (yani bu örnekte JUnit) bağlamamızı sağlar. Arquillian bileşen ve entegrasyon testi için kullanılıyorsa, Pact'ı Arquillian testine bağlamak için Arquillian Algeron'u kullanabiliriz. Alegeron, Arquillian testlerinde kullanımı kolaylaştırmak için Pact'i genişletti ve ayrıca genellikle kendiniz manuel olarak oluşturmanız gereken bir özellik de ekler, yani test sırasında bir temsilciye sözleşmeleri otomatik olarak yayınlar veya bir temsilciden sözleşmeleri indirir. Bu özellik, CI veya CD ardışık düzenleri için gereklidir. Java uygulamalarında kullanıcı sözleşmesi testi yapmak için, Arquillian ve Arquillian Algeron'a dikkat etmenizi şiddetle tavsiye ederim.

Bir PactDslJsonBody kod parçacığı oluşturabilir ve "joker karakter" veya "bu alandaki her şeyi ilet" sözdizimini kullanabiliriz. Örneğin, "varsayılan değeri olan X adında bir öznitelik olacağını" belirtmek için body.integerType ("attr_name", default_value) kullanırız. Varsayılan değer parametresi kaldırılırsa, değer aslında herhangi bir değer olabilir. Bu kod parçacığında, sadece isteğin yapısını belirtiyoruz. Burada sentetik (sentetik) bir özellik belirttiğimizi unutmayın. Ve özniteliği doğru olan her istek için belirli bir yapıya sahip bir yanıt değeri olacaktır.

Burada, kullanıcı sözleşmesini (yanıt değeri) beyan ederiz:

özel DslPart sentetikBookingResponseBody {PactDslJsonBody body = new PactDslJsonBody; body .booleanType ("sentetik", doğru); dönüş gövdesi;}

Bu çok basit bir örnek: Bu test için, yanıt değerinin bir özniteliğe sahip olmasını beklemekteyiz: "sentetik: doğru". Bu önemlidir çünkü sentetik bir rezervasyon gönderirken, Siparişler hizmetinin rezervasyonun gerçekten sentetik bir istek olarak işlendiğini doğrulamasını istiyoruz. Bu test başarılı bir şekilde çalışırsa, bu Pact sözleşmesini hedef derleme dizininde oluşturacağız. (Bu makale örneğinde, ./target/pacts adresinde görünecektir.)

{"provider": {"name": "orders_service"}, "tüketici": {"name": "test_synthetic_order"}, "etkileşimler": , "metadata": {"pact-specation": {"version": "3.0.0"}, "pact-jvm": {"sürüm": ""}}}

Burada sözleşme Git, Sözleşme Aracısı veya paylaşılan dosya sistemine konulabilir. Tedarik tarafında (Siparişler hizmeti), sağlayıcı tarafından sağlanan hizmetin kullanıcı sözleşmesindeki beklentileri gerçekten karşıladığından emin olmak için bir bileşen testi oluşturabiliriz. Hepsi test edilebilen birden fazla kullanıcı sözleşmesi olabileceği unutulmamalıdır (özellikle tedarikçi tarafından sağlanan hizmette değişiklik yaptığımızda, etkilenebilecek alt kullanıcıları anlamak için etki testini kullanabiliriz)

@RunWith (PactRunner.class) @Provider ("orders_service") @PactFolder ("pact /") public class ConsumerContractTest {private static ConfigurableApplicationContext applicationContext; @TestTarget public final Target target = new HttpTarget (8080); @BeforeClass public statik void start {applicationContext = SpringApplication.run (Application.class);} @State ("kullanılabilir gösteriler") public void testDefaultState {System.out.println ("hi");}}

Lütfen bu basit gösteride sözleşmeyi dosya sistemindeki bir klasörden ekteki ./pacts altında çıkaracağımızı unutmayın.

Kullanıcı odaklı sözleşme testi benimsendiğinde, hizmette daha özgür bir şekilde değişiklik yapabiliriz. Bu sorunun çalışan bir örneği için arka uç-v2 hizmetine ve tedarikçi Siparişleri hizmeti örneğine bakın.

5 Kanarya testi veya yeni mikro hizmetleri kullanıma sunma

Dikkat edilecek noktaları gözden geçirin

  • Grubu belirleyin ve yeni mikro hizmete gerçek zamanlı işlem trafiği gönderin

  • Veritabanına doğrudan bağlantı hala gereklidir, çünkü bu süre boyunca işlem hala iki kod yolundan geçecektir.

  • Tüm trafik mikro hizmetlere aktarıldıktan sonra, eski özellikleri terk etme zamanı gelir

  • Mikro hizmetlere gerçek zamanlı trafik gönderdikten sonra, eski kod yoluna geri dönmenin zorluklarla karşılaşacağını ve koordinasyon gerektireceğini lütfen unutmayın.

Bu senaryonun bir diğer önemli kısmı, trafiğin küçük bir bölümünü imzalı yeni bir dağıtım yoluyla göndermemiz gerektiğidir. Aranan arka ucu hassas bir şekilde kontrol etmek için Istio'yu kullanabiliriz. Örneğin, tamamen piyasaya sürülen ve üretim yükünü kabul eden arka uç-v1'i dağıttık. Arka uç-v2'yi dağıttığımızda ve yeni kod yolunu kontrol etmek için bir özellik işaretine sahip olduğunda, önceki makaledeki uygulamaya benzer şekilde, Canary sürümü için Istio'yu kullanabiliriz. Trafiğin yalnızca% 1'ini göndererek başlayın ve ardından yavaşça artırın (% 5,% 25, vb.) Ve gönderirken etkiye dikkat edin. Hem eski kod yolunu hem de yeni kod yolunu etkinleştirmek için bu özellikleri de değiştirebiliriz. Bu, mikro hizmet mimarisi değişiklikleri ve geçiş riskini büyük ölçüde azaltmamıza yardımcı olabilecek çok güçlü bir teknolojidir. Aşağıda bir istio rota kuralı örneği verilmiştir:

apiVersion: config.istio.io/v1alpha2 tür: RouteRule meta verisi: ad: arka uç-v2 belirtim: hedef: ad: arka uç önceliği: 20 yol: -etiketler: sürüm: v1 ağırlık: 99-etiketler: sürüm: v2 ağırlık: 1

Unutulmaması gereken bazı noktalar: Bu noktada, hem eski kod yolunu hem de yeni kod yolunu etkinleştirmek için yeni Siparişler hizmetini kullanabiliriz ve yeni Siparişler hizmeti sentetik işlemler gerçekleştirecektir. Şimdiye kadar, açıklanan kanarya herhangi bir trafiğin% 1'i için geçerli olacaktır. Bu, yalnızca dahili kullanıcılara veya az sayıda harici kullanıcıya yayınlıyorsanız ve bunları gerçek zamanlı Siparişler hizmeti (yani, simüle edilmemiş trafik) aracılığıyla yayınlıyorsanız yararlı olabilir. Kullanıcı tabanlı değişiklik yolunu, kullanıcıları kuyruklar halinde gruplandıran FF4j yapılandırmasıyla birleştirerek, yeni Siparişler hizmetinin tam kod yolunu etkinleştirebiliriz (gerçek zamanlı trafik, sentetik olmayan işlem yükleri vb. Dahil). Bununla birlikte, bunun anahtarı, kullanıcı Emirlerin gerçek zamanlı kod yoluna yönlendirildikten sonra, gelecekteki aramaların rahatlığı için her zaman bu şekilde gönderilecektir. Bunun nedeni, yeni hizmetle bir sipariş verildiğinde, Emirlerin monolitik uygulamanın veritabanında görünmemesidir. Bu kullanıcıya yönelik tüm sorgular veya güncellemeler her zaman yeni mikro hizmetten geçmelidir.

Bu noktada trafik kalıplarını veya hizmet performansını gözlemleyip sürüm kapsamını artırıp artırmama kararı alabiliyoruz. Nihayetinde amacımız tüm trafiği yeni hizmete göndermektir.

Ya veriler monolitik uygulamada değilse? Hiçbir şey yapmamayı seçebilirsiniz - yeni Siparişler hizmeti artık siparişin veya rezervasyon mantığının yanı sıra verilerin gerçek sahibidir. Bu yeni Siparişler için, monolitik uygulamalar arasında entegrasyonun gerekli olduğunu düşünüyorsanız, etkinlikleri ve sipariş ayrıntılarını yeni Siparişler hizmetinde yayınlamayı seçebilirsiniz. Bu şekilde monolitik uygulama da bu olayları yakalayabilir ve veritabanında saklayabilir. Diğer servisler de bu olayları dinleyebilir ve bunlara tepki verebilir. Etkinlik yayınlama mekanizması hala kullanışlıdır.

Tamam, bu blog yazısı yeterince uzun! Genel içerikte kalan iki alt bölüm vardır: "Çevrimdışı Veri Çıkarma Dönüşüm Yükleme (ETL) veya Taşıma" ve "Bağlantıyı Kesme veya Veri Depolamasını Kesme". İçeriğin bu kısmını doğru bir şekilde ele almak istediğim için burada bitirmeliyim ve geri kalanı dördüncü bölümde sunulacak! Beşinci bölüm, genel içeriği gösteren bir web yayını veya video veya demo sunumu olacaktır.

Referans adresi:

https://projects.spring.io/spring-boot/

https://github.com/teiid/teiid-spring-boot

https://istio.io

https://ff4j.org

https://kubernetes.io

https://www.openshift.org

https://fabric8.io

https://github.com/pact-foundation/pact-specification

https://hoverfly.io

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

https://twitter.com/VaughnVernon

https://vaughnvernon.co/?p=838

https://github.com/ticket-monster-msa/monolith/tree/master/orders-service

https://twitter.com/rareddy

https://github.com/teiid/teiid-spring-boot/blob/master/docs/UserGuide.adoc

https://github.com/ticket-monster-msa/monolith/tree/master/orders-service/src/main/java/org/ticketmonster/orders/domain

https://github.com/ticket-monster-msa/monolith/blob/master/orders-service/src/main/java/org/ticketmonster/orders/domain/Ticket.java

https://github.com/teiid/teiid-spring-boot/blob/master/docs/Reference.adoc

https://github.com/teiid/teiid-spring-boot/blob/master/samples/rdbms/src/main/java/org/teiid/spring/example/CustomerRepository.java

https://github.com/teiid/teiid-spring-boot/tree/master/odata

https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

https://ff4j.org

https://blog.turbinelabs.io/deploy-not-equal-release-part-one-4724bc1e726b

https://github.com/ff4j/ff4j/wiki/Advanced-Concepts

https://github.com/ticket-monster-msa/monolith/blob/master/backend-v2/src/main/java/org/jboss/examples/ticketmonster/util/FF4jFactory.java

https://github.com/ff4j/ff4j/wiki/Store-Technologies

https://github.com/ff4j/ff4j/wiki/Web-Concepts#web-console

https://martinfowler.com/articles/consumerDrivenContracts.html

https://github.com/pact-foundation/pact-specification

https://twitter.com/dius_au

https://docs.pact.io/documentation/

https://github.com/ticket-monster-msa/monolith/tree/master/backend-v2

https://github.com/DiUS/pact-jvm

https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-provider-junit

https://github.com/ticket-monster-msa/monolith/tree/master/backend-v2

https://github.com/ticket-monster-msa/monolith/tree/master/orders-service

EAWorld DevOps 312

Razer Phone 2 resmi olarak piyasaya sürüldü: Snapdragon 845 amiral gemisi 120Hz yenileme hızı kullanıyor
önceki
Zhang Ruoyun'un yeni dizisi hit, kadın kahraman Yang Mi için her zaman azarlanır ve ikinci erkek "Aşk Dairesi" ile popüler olur.
Sonraki
190326 İşbirliği yapan insanlar, Li Yifengin gerçek görünümünü gözlerinden geri kazanmanın iyi olduğunu söylediler.
Insta360 ONE X, panoramik kamera oynamanın N yolu
Hu Shiwei ile diyalog: Üç büyük devlete ait bankanın hisselerini aynı anda aldıktan sonra, dördüncü paradigma yapay zekanın değerini işletmelere sunmalıdır.
"Her şey yolunda" Ming Cheng eskiyi aldattı ve Ming Yu'yu hastanede dövdü, Guo Jingfei şunları yazdı: Bir dahaki sefere iyi bir insan olmak istiyorum
"YÜZDE DOKUZ" "Haber" 190326 Cai Xukun, Şangay'dan ayrılıyor ve Pekin'e uçuyor. Retro küçük usta bugün harika
Kuyruk yolculuğu, gerçekten tütsü uyarısı: birinci taraf, Seul'un tadı
9 yıldır peşinde olan ünlü Amerikan draması "The Walking Dead" nihayet bitmedi.
"StarCraft 2: Nova Secret Action" incelemesi: sonunda geri döndü
Jia Jingwen'in eski kocası ve Xiu Jiekai aynı gün doğum günlerini kutladı ve Wutong kız kardeşinin yüksek EQ'su her iki tarafı da kutsadı
190326 Arkeoloji "Zhu Xian" Baguio'nun başlangıç haritası Güzel ve güzel yürüyüş!
Bir tarayıcıyla madencilik için bir kod simüle ettim, çok karmaşık değil ama yanlış gitme
Uzun bir süre sonra elektrikli araba kullanmaya neden alışamıyorum?
To Top