Microservice Mimarilerinde Consumer Driven Contracts Testing Nedir? ve C# ile Implementasyon

Merhaba arkadaşlar.

Yine microservice mimarilerine yönelik bir konu ile karşınızdayım. Sizlerde biliyorsunuz ki son dönemlerde neredeyse tüm ilgi alanımı, microservice mimarileri ve MQ(Message Queue) tabanlı sistemlere yoğunlaştırdım. Bu sistemler her ne kadar bir çok derdimizi çözseler bile, asla kusursuz bir şey olmadığı gibi beraberlerinde getirdiği bazı ek maliyetler de bulunmaktadır.

Microservice mimarilerine geçilmesinde, belki en önemli concern’lerden birisi de test konusudur. Peki nasıl?

Sistemimizde -n tane servis’in var olduğunu düşünelim ve hepsi loosely coupled bir şekilde hayatlarını sürdürmektedir. Biliyoruz ki bitmek bilmeyen business ihtiyaçlarının karşısında, sisteme sürekli ek özellikler katılmaktadır. Bunların yanı sıra gerçek hayatta legacy application’ların, bir anda yeni mimarilere de geçirilebilmesi de mümkün olmamaktadır. Bu sebep ile her bir modülü parça parça microservis’lere taşımamız gerekebileceği için sürekli bir deployment ihtiyacı doğacaktır. Peki bu deployment’lar sırasında hangi servisin nereyi etkileyeceğini nasıl bilebiliriz ve ilgili servisi tüketen diğer servislerin bozulmayacağını nasıl test edebiliriz?

En sık karşılaşılan durumlardan birisi, endpoint’lerin client’ın etkileşimi olmaksızın değiştirilmesidir.

Örneğin:

  • GET /api/customer şeklinde iken, GET /api/customers olarak endpoint’in değiştirilmesi
  • GET /api/customers/1 şeklinde müşteri bilgilerini dönerken, artık GET /api/customers/1/detail şeklinde dönülmesi gibi senaryolar

İşte bu tarz durumlar karşısında insan bazen, keşke monolith bir application geliştiriyor olsaydık demiyor değil. 🙂 İşin geyiği bir yana monolith bir application için integration test’ler yazmak, birden çok distributed microservice’lere implemente etmekten çok daha basit.

Konumuza geri dönecek olursak eğer, microservice yapılarında bu tarz problemler için aslında yeni olmayan bir yaklaşım/pattern mevcuttur. Bir kaç dönemdir microservice mimarilerinin popülerleşmesi ile önem kazanan bu yaklaşım, Consumer Driven Contracts Testing olarak geçmektedir.

Consumer Driven Contract temel olarak API contract’larının kullanımlarını client’lara tanımlayarak, değişimler sırasında yaşanabilecek problemlerin bir nebze önüne geçebilmeye yardımcı olmaktadır. Consumer Driven Contracts’ı .NET tarafında implementasyonu için Pact isminde harika bir framework mevcuttur. Makale sırasında gerçekleştirecek olduğumuz örneğimizde, Pact framework’ünü kullanarak contract specification, contract verification ve contract distribution nasıl gerçekleştirilir gibi konuları ele alıyor olacağız.

Consumer Driven Contracts Testing Nasıl Çalışır?

Consumer Driven Contracts’ın çalışma mantığı basic concept olarak aşağıdaki gibidir.

  1. Consumer, ilgili service’in request ve response’undan ne beklediğini tanımlar
  2. Provider ve Consumer bu contract’lar için verify işlemini gerçekleştirir
  3. Sonrasında ise Provider, ilgili contract’ların yerine getirildiğini sürekli olarak doğrular ve bu sayede hangi Consumer’ların değişimler karşısında nasıl etkilendikleri görünür kılınır

Pact Framework’ünün Bazı Avantajları

  • End-to-end olarak ortamları kurmaya gerek yoktur
  • Manuel testing gerektirmez
  • Contract’ların üretimi ve doğrulanması Pact tarafından otomatik olarak gerçekleştirilir
  • Oluşturulan Pact’ler sayesinde de, readable bir şekilde Api Contract dokümantasyonu da oluşturulmuş olunur

Bunlara ek olarak, farklı teknolojiler ile geliştirilmiş service’ler arasındaki entegrasyonu da mümkün kılar. Pact initial olarak Ruby için geliştirilmiş bir framework’dür ve şuan bir çok programlama dilinde yaygın bir hale gelmiş durumdadır. Contract verify işlemi için oluşturulacak olan pact file’ı JSON formatında olacağı için, farklı platformlar arasında da kolaylıkla implemente edilebilmesi mümkündür.

Pact Framework’ünün Implementasyonu

1) Consumer ve Pact’lerinin Oluşturulması

Implementasyon sırasında ilk gerçekleştirecek olduğumuz kısım, yukarıdaki diyagramın sol tarafı olacaktır. Burada sonrasında oluşturacak olduğumuz Provider’ı yani API’ı consume edecek bir Consumer yazacağız. Consumer içerisinde API contract’larına yönelik request ve response’ların Pact’leri çıkartıp, file system’da verify işlemi için persist edeceğiz.

İkinci kısımda oluşturacak olduğumuz Provider, müşteri bilgilerini çeken bir API olsun. Bu API’ın GET method’u “/api/customers/1” URI’ı ile, “1” numaralı id’ye sahip customer’ı dönsün. Öncelikle ilk adım olarak serialization işlemlerinde kolaylık sağlaması açısından, API’dan repsonse olarak dönüyor olacağımız contract’ı ekleyerek başlayalım. Bunun için “ConsumerDrivenContractsTestingSample” isminde yeni bir blank solution oluşturup, içerisine “Contracts” isminde bir class library ekleyelim.

Eklemiş olduğumuz bu library içerisinde, “Customer.cs” isminde yeni bir class ekleyelim ve aşağıdaki gibi kodlayalım.

namespace Contracts
{
    public class Customer
    {
        public int Id { get; set; }
        public string FullName { get; set; }
    }
}

Contract kısmı bu kadar.

Diyagramın ilk kısımdaki Consumer tarafını kodlamaya başlayabiliriz. Solution üzerine structured görünebilmesi açısından “Service Consumer” isminde bir klasör oluşturup, içerisine “CustomerApiServiceConsumer” isminde bir class library ekleyelim. Bu library içerisinde, biraz önce bahsetmiş olduğum Customer API’ı consume edebilmemizi sağlayan client kısmını kodlayacağız. Client’ı oluşturma işlemine başlamadan önce REST call’ları sırasında, kolaylık sağlaması açısından Nuget Package Manager üzerinden “RestSharp” paketini “CustomerApiServiceConsumer” library’sine kuralım ve “Contracts” library’sini referans olarak da gösterelim.

Kurulum işleminden sonra “CustomerApiClient.cs” isminde yeni bir class ekleyelim ve client’ı aşağıdaki gibi kodlayalım.

using Contracts;
using RestSharp;

namespace CustomerApiServiceConsumer
{
    public class CustomerApiClient
    {
        private readonly RestClient _restClient;

        public CustomerApiClient(string baseUri)
        {
            _restClient = new RestClient(baseUri);
        }

        public Customer Get(int id)
        {
            var request = new RestRequest($"/api/customers/{id}", Method.GET) {RequestFormat = DataFormat.Json};
            request.AddHeader("Accept", "application/json");

            var result = _restClient.Execute<Customer>(request);

            return result.Data;
        }
    }
}

Oluşturmuş olduğumuz “CustomerApiClient” içerisinde “RestSharp” ın client’ını kullanarak, “/api/customers/{id}” resource’una basic bir GET request’i yapıyoruz. Request sonucunda ise geriye “Customer” result’ını dönüyoruz.

Şimdi sıra geldi test projesini oluşturmaya. Oluşturacak olduğumuz bu test projesi, servis’i kullanacak olan service consumer’ı temsil ediyor olacak ve burada “Customer API” a yönelik Pact‘leri çıkartıp, test case’lerini yazacağız. Solution üzerindeki “Service Consumer” klasörünün içerisine, “CustomerApiServiceConsumer.Tests” isminde yeni bir Unit Test projesi oluşturalım. Unit Test projesinin üzerine, öncelikle test işlemlerinde bazı fonksiyonlarından yararlanabilmek için “xunit” test framework’ünü ve “xunit.runner.visualstudio” paketini, Nuget Package Manager üzerinden kuralım. Kurulum işleminin ardından makale girişinde de bahsetmiş olduğumuz, Consumer Driven Contracts Testing yazabilmemize olanak sağlayan “PactNet” paketini de Nuget Package Manager üzerinden kuralım. Nuget üzerindeki kurulumların ardından, “Contracts” ve “CustomerApiServiceConsumer” library’lerini referans olarak ekleyelim.

Burada kodlamaya ilk olarak Customer API’ın pact’ini oluşturmak ile başlayacağız. Bu kısımlarda referans aldığım yer ise, Pact framework’ünün .net implementasyon kısmının bulunduğu github adresidir. Makale sonunda sizlerle paylaşıyor olacağım. Konumuza devam edecek olursak, “ConsumerCustomerApiPact.cs” isminde yeni bir class ekleyelim ve aşağıdaki gibi kodlayalım.

using System;
using PactNet;
using PactNet.Mocks.MockHttpService;

namespace CustomerApiServiceConsumer.Tests
{
    public class ConsumerCustomerApiPact : IDisposable
    {
        public IPactBuilder PactBuilder { get; private set; }
        public IMockProviderService MockProviderService { get; private set; }

        public int MockServerPort => 1234;
        public string MockProviderServiceBaseUri => $"http://localhost:{MockServerPort}";

        public ConsumerCustomerApiPact()
        {
            PactBuilder = new PactBuilder();
            PactBuilder.ServiceConsumer("CustomerApiServiceConsumer").HasPactWith("CustomerApi.Host");

            MockProviderService = PactBuilder.MockService(MockServerPort);
        }

        public void Dispose()
        {
            PactBuilder.Build();
        }
    }
}

“ConsumerCustomerApiPact” class’ı içerisinde bulunan “PactBuilder” ile, bir adet mock servis ayağa kaldırıyoruz. Bu mock servis, “MockServerPort” property’si üzerinden almış olduğu port bilgisi ile arka planında Nancy Self Hosting paketini kullanarak oluşmaktadır. Buradaki dikkat edilmesi gereken nokta ise “PactBuilder.ServiceConsumer()” method’unda servis’i consume edecek yerin neresi olduğu, “HasPactWith()” method’unda ise hangi provider ile pact’e sahip olduğunu belirtiyoruz. Dispose method’u ile gerçekleştirilen deferred execution işlemi ile bu pact’i kullanacak olan test case’inin, ilgili test senaryosunu gerçekleştirmesinden sonra Customer API’a ait pact’leri oluşturması sağlanmaktadır.

Şimdi yine “CustomerApiServiceConsumer.Tests” projesi içerisine, “CustomerApiConsumerTests.cs” isminde bir Unit Test class’ı ekleyelim ve aşağıdaki gibi kodlayalım.

using System.Collections.Generic;
using Contracts;
using PactNet.Mocks.MockHttpService;
using PactNet.Mocks.MockHttpService.Models;
using Xunit;

namespace CustomerApiServiceConsumer.Tests
{
    public class CustomerApiConsumerTests : IClassFixture<ConsumerCustomerApiPact>
    {
        private readonly IMockProviderService _mockProviderService;
        private readonly string _mockProviderServiceBaseUri;

        public CustomerApiConsumerTests(ConsumerCustomerApiPact data)
        {
            _mockProviderService = data.MockProviderService;
            _mockProviderServiceBaseUri = data.MockProviderServiceBaseUri;

            data.MockProviderService.ClearInteractions();
        }

        [Fact]
        public void GetCustomer_WhenTheCustomerIdGreaterThanZero_ReturnCustomer()
        {
            //Arrange
            int customerId = 1;

            _mockProviderService
                .Given("There is a customer with id 1")
                .UponReceiving("A GET request to retrieve the customer")
                .With(new ProviderServiceRequest
                {
                    Method = HttpVerb.Get,
                    Path = "/api/customers/" + customerId,
                    Headers = new Dictionary<string, string>
                    {
                        {"Accept", "application/json"}
                    }
                })
                .WillRespondWith(new ProviderServiceResponse
                {
                    Status = 200,
                    Headers = new Dictionary<string, string>
                    {
                        {"Content-Type", "application/json; charset=utf-8"}
                    },
                    Body = new Customer
                    {
                        Id = 1,
                        FullName = "Gökhan Gökalp"
                    }
                });

            var consumer = new CustomerApiClient(_mockProviderServiceBaseUri);

            //Act
            var result = consumer.Get(1);

            //Assert
            Assert.NotNull(result);

            _mockProviderService.VerifyInteractions();
        }
    }
}

“xunit” test paketi ile gelen “IClassFixture<T>” interface’inin içerisine set ettiğimiz “ConsumerCustomerApiPact” class’ını, herhangi bir test case’i execute olmadan önce pact class’ını execute ediyor ve constructor üzerinden pact’in instance’ını inject ediyor. “MockProviderService.ClearInteractions()” method’u ile de, önceden kayıt edilmiş bir interaction varsa, test run edilmeden önce temizlemektedir.

Şimdi gelelim asıl test case’ine. “GetCustomer_WhenTheCustomerIdGreaterThanZero_ReturnCustomer” ismindeki test case’i ile isminden de anlaşılabileceği gibi, customer id’si sıfırdan büyük ise ilgili customer’ı getirmesini istiyoruz. Buradaki işlemleri fluent bir şekilde “ConsumerCustomerApiPact” içerisinden gelen “IMockProviderService” ile gerçekleştiriyoruz. Burada yer alan “Given()” ve “UponReceiving()” fluent method’ları ile, pact’in readability açısından anlaşılır olabilmesi için ne alınacağını ve ne alındığı gibi bilgileri giriyoruz.”With()” ve “WillRespondWith” fluent method’ları ile ise, provider’a nasıl bir request geçeceğimizi ve bunun sonucunda nasıl bir response elde edeceğimizi tanımlıyoruz. Dikkat ederseniz bu noktada aslında bir nevi provider’ın endpoint dokümantasyonunu da oluşturuyor gibiyiz. Aslında gibiyiz de değil, oluşturuyoruz her bir endpoint için. 🙂

Sonrasında ise artık, öncesinde oluşturmuş olduğumuz client’ı kullanarak, “_mockProviderServiceBaseUri” ile mock servis üzerinden “consumer.Get(1);” method’unu çağırıyoruz. Bu işlemin sonucunda eğer result null değilse, pact’i oluşturabilmesi için interaction’ı verify ederek, mock provider üzerinde tekrardan kayıtlı hale getiriyoruz.

Test Explorer üzerinden “GetCustomer_WhenTheCustomerIdGreaterThanZero_ReturnCustomer” method’unu çalıştıralım ve sorunsuz bir şekilde implementasyonu gerçekleştirdi isek, test’in başarılı bir şekilde geçtiğini görebileceğiz.

Yukarıdaki resimde görüldüğü gibi test’i çalıştırdığımda başarılı bir şekilde test işlemi gerçekleşti. Şimdi asıl bu işlemin sonucunda hatırlarsak “ConsumerCustomerApiPact” oluştururken Dispose kısmında gerçekleştirilen deferred execution ile “MockProviderService” üzerine register edilmiş request ve beklenen response senaryoları buradan”Build()” method’u ile tetikleniyordu. Bu işlem pact file’ının “\CustomerApiServiceConsumer.Tests\pacts” dizini altında aşağıdaki gibi oluşmasını sağlamıştır.

{
  "provider": {
    "name": "CustomerApi.Host"
  },
  "consumer": {
    "name": "CustomerApiServiceConsumer"
  },
  "interactions": [
    {
      "description": "A GET request to retrieve the customer",
      "provider_state": "There is a customer with id 1",
      "request": {
        "method": "get",
        "path": "/api/customers/1",
        "headers": {
          "Accept": "application/json"
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": {
          "Id": 1,
          "FullName": "Gökhan Gökalp"
        }
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

Pact dosyası ne kadar da okunabilir değil mi? Artık Provider kısmını hazırlamaya başlayabiliriz.

2) Provider’ın Oluşturulması ve Verification

Artık bu kısımda Provider üzerinden Consumer’ın oluşturmuş olduğu pact’leri alarak, test API server’ı üzerinden pact’ler doğrultusunda verify işlemini gerçekleştireceğiz. Solution üzerine “Service Provider” isminde yeni bir klasör daha ekleyerek, içerisine “CustomerApi.Host” isminde bir console application projesi oluşturalım. Projeyi oluşturduktan sonra Nuget Package Manager üzerinden “Microsoft.AspNet.WebApi.OwinSelfHost” paketini kuralım.

Kurulum işlemlerinin ardından API’ı owin ile host edebilmek için “Startup.cs” isminde yeni bir class ekleyelim ve aşağıdaki gibi kodlayalım.

using System.Web.Http;
using Owin;

namespace CustomerApi.Host
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Configure Web API for self-host. 
            var config = new HttpConfiguration();

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            app.UseWebApi(config);
        }
    }
}

Startup class’ı ile, owin üzerinden API’ın host işlemini gerçekleştireceğiz. Host işlemi için “Program.cs” class’ını aşağıdaki gibi güncelleyelim.

using System;
using Microsoft.Owin.Hosting;

namespace CustomerApi.Host
{
    class Program
    {
        static void Main(string[] args)
        {
            using (WebApp.Start<Startup>("http://localhost:8090"))
            {
                Console.WriteLine("Web Server is running.");
                Console.WriteLine("Press any key to quit.");
                Console.ReadLine();
            }
        }
    }
}

“Microsoft.Owin.Hosting” namespace’i altında bulunan “WebApp” ile API’ı, localhost üzerinden “8090” portu üzerinden host edeceğiz. Şimdi sıra geldi controller’ı eklemeye. Bunun için “Controllers” isminde yeni bir klasör oluşturup, “CustomerController.cs” class’ını içerisine ekleyelim ve aşağıdaki gibi kodlayalım.

using System.Web.Http;
using Contracts;

namespace CustomerApi.Host.Controllers
{
    [RoutePrefix("api/customers")]
    public class CustomerController : ApiController
    {
        [HttpGet, Route("{id}")]
        public Customer Get(int id)
        {
            if (id > 0)
            {
                return new Customer
                {
                    Id = 1,
                    FullName = "Gökhan Gökalp"
                };
            }

            return null;
        }
    }
}

Makale girişinde de bahsettiğimiz gibi artık “api/customers/{id}” URI’ı ile, “0” dan büyük bir id değeri geldiğinde geriye “Gökhan Gökalp” kullanıcısını dönecektir.

Test edebilmek için “CustomerApi.Host” projesini çalıştıralım ve “http://localhost:8090/api/customers/1” URI’ına bir GET request’inde bulunalım.

Yukarıdaki resimde olduğu gibi “1” numaralı id’ye sahip “Gökhan Gökalp” kullanıcısının, response olarak geldiğini görüyoruz.

Şimdi “CustomerApi.Host” projesi için bir Unit Test projesi oluşturacağız ve burada consumer’ın oluşturmuş olduğu pact’lerin verify işlemlerini gerçekleştireceğiz. “Service Provider” klasörü içerisine “CustomerApi.Tests” isminde yeni bir Unit Test projesi oluşturalım ve projenin oluşturma işleminin ardından Nuget Package Manager üzerinden “xunit”, “xunit.runner.visualstudio” “PactNet” ve “Microsoft.Owin.Testing” paketlerini kuralım. Paketlerin kurulum işleminin tamamlanmasından sonra “CustomerApi.Host” projesini de referans olarak ekleyelim.

İhtiyaç duyduğumuz paketler hazır olduğuna göre “CustomerTests.cs” isminde bir Unit Test class’ı ekleyelim ve aşağıdaki gibi kodlayalım.

using CustomerApi.Host;
using Microsoft.Owin.Testing;
using PactNet;
using Xunit;

namespace CustomerApi.Tests
{
    public class CustomerTests
    {
        [Fact]
        public void EnsureCustomerApiHonoursPactWithConsumer()
        {
            //Arrange
            IPactVerifier pactVerifier = new PactVerifier(() => { }, () => { });

            pactVerifier
                .ProviderState("There is a customer with id 1");

            //Act / Assert
            using (var testServer = TestServer.Create<Startup>())
            {
                pactVerifier
                .ServiceProvider("CustomerApi.Host", testServer.HttpClient)
                .HonoursPactWith("CustomerApiServiceConsumer")
                .PactUri("../../../CustomerApiServiceConsumer.Tests/pacts/customerapiserviceconsumer-customerapi.host.json")
                .Verify();
            }
        }
    }
}

Burada oluşturmuş olduğumuz “EnsureCustomerApiHonoursPactWithConsumer” method’unun içerisinde bir “PactVerifier” tanımlıyoruz. Sonrasında ise verifier üzerinden provider’ın state’ini, “ServiceProvider()” fluent method’u ile hangi provider olduğunu ve Owin TestServer’ı üzerinden gelen http client’ını parameter olarak set ediyoruz. “HonoursPactWith()” fluent method’u ile pact’i oluşturan consumer’ın name’ini ve “PactUri()” fluent method’u ile de oluşturulmuş olan pact’in URI adresini parametre olarak set ediyoruz.

Tüm parametrelerin set edilmesinden sonra “Verify()” method’unu çağırarak, provider’ın mock test server’ı üzerinden consumer tarafından oluşturulmuş olan pact’lerin, verify işleminin gerçekleştirilmesini sağlamış oluyoruz. Tüm implementasyonumuz tamamlanmış durumda. Hatırlarsak consumer tarafını implemente ederken, test etmek amaçlı Unit Test case’ini çalıştırmıştık ve başarılı bir şekilde pact file’ı oluşmuştu. Şimdi ise verify işlemini deneyebilmek için gelin “EnsureCustomerApiHonoursPactWithConsumer” test case’ini çalıştıralım ve sonucuna bir bakalım.

Evet bu test case’i de başarılı bir şekilde sonuçlandı.

Bu demek oluyor ki pact üzerinde consumer’ın provider’dan beklemiş olduğu request ve response modelleri, mock test server’ı üzerinden çalıştırılarak başarılı bir şekilde verify edildi. Şimdi gelelim gerçek hayatta karşılaşabileceğimiz bir problemi test etmeye. Diyelim ki Customer API müşteri detaylarını artık “/api/customers/1” şeklinde değil de, “/api/customers/1/detail” şeklinde veriyor olarak güncellensin. Bunun için hemen ilgili controller’ı açarak aşağıdaki gibi route’u güncelleyelim.

using System.Web.Http;
using Contracts;

namespace CustomerApi.Host.Controllers
{
    [RoutePrefix("api/customers")]
    public class CustomerController : ApiController
    {
        [HttpGet, Route("{id}/detail")]
        public Customer Get(int id)
        {
            if (id > 0)
            {
                return new Customer
                {
                    Id = 1,
                    FullName = "Gökhan Gökalp"
                };
            }

            return null;
        }
    }
}

Controller’ı güncelleme işleminden sonra solution’ı derleyelim ve tekrardan “EnsureCustomerApiHonoursPactWithConsumer” test case’ini çalıştıralım.

Ups! Bu sefer test case’i başarılı bir şekilde çalıştırılamadı. Hatanın detaylı açıklamasını ise test case’inin bulunduğu projenin klasörü altında, “logs” isminde yeni bir klasör oluşturarak “providerName.host_verifier” isminde bir log dosyası içerisinde tutmaktadır.

Bu case için PactNet’in oluşturmuş olduğu “customerapi.host_verifier.log” dosyası ise aşağıdaki gibidir.

2017-01-21 18:04:21.080 +03:00 [Debug] Verifying a Pact between CustomerApiServiceConsumer and CustomerApi.Host
  Given There is a customer with id 1
    A GET request to retrieve the customer
      with GET /api/customers/1
        returns a response which
          has status code 200
          includes headers
            'Content-Type' with value application/json; charset=utf-8
          has a matching body
2017-01-21 18:10:47.502 +03:00 [Debug] Verifying a Pact between CustomerApiServiceConsumer and CustomerApi.Host
  Given There is a customer with id 1
    A GET request to retrieve the customer
      with GET /api/customers/1
        returns a response which
          has status code 200 (FAILED - 1)
          includes headers
            'Content-Type' with value application/json; charset=utf-8
          has a matching body (FAILED - 2)

Failures:

1) Expected: 200, Actual: 404

2) Expected: {
  "Id": 1,
  "FullName": "Gökhan Gökalp"
}, Actual: {
  "Message": "No HTTP resource was found that matches the request URI 'http://localhost/api/customers/1'."
}

Yukarıdaki log satırları aslında gayet anlaşılır bir şekilde açık. PactNet burada, consumer’ın ne istediği ve ne beklediğini provider tarafından sağlamaya çalışıyor. “Failures” altında ise HTTP status’ü olarak “200” beklediğini ancak “404” aldığını ve “1” numaralı müşteriye ait bilgileri alması gerekirken, “No HTTP resource was found…” şeklinde bir hata mesajı ile karşılaştığını söylüyor. Ne kadar harika değil mi?

Consumer Driven Contract Testing bize, değişen ihtiyaçlar karşısında deployment yapmadan önce nerelerin etkilenebileceğini hızlı bir şekilde ulaşabilmemizi sağlamaktadır. Özellikle microservice yapılarının bulunduğu ortamlarda, nerelerin etkilenebileceği tarz concern’lerin giderilebilmesi için kullanılması gerekmektedir diye düşünüyorum son zamanlarda. Bunlara ek olarak birde az önce “PactUri()” fluent method’u ile consumer’ın oluşturmuş olduğu pact’in adresini set etmiştik. Bu pact’leri dilersek network üzerinde ortak bir yerde de persist ederek, verify işlemini oradan gerçekleştirebilmek de mümkündür.

Umarım yararlı ve keyifli bir makale olmuştur. Makale sırasında gerçekleştirmiş olduğumuz örnek projeye, aşağıdaki github hesabım üzerinden erişebilirsiniz.

https://github.com/GokGokalp/consumer-driven-contracts-testing-sample

Kaynaklar:

https://github.com/SEEK-Jobs/pact-net
https://martinfowler.com/articles/consumerDrivenContracts.html
https://tech.affinitas.de/?p=51

Gökhan Gökalp

View Comments

  • Sormak istediğim bir soru var. MockProviderServiceBaseUri adresi olarak localhost:1234 veriyoruz fakat producer uygulamayı ayağa kaldırmadan hatta (özellikle denedim) ilk çalıştırma bile yapmadan (github da paylaştığınız solition ı indirip producer api projesini sildim) yine de httprequest atınca istenilen sonuç geldi elime. Bunu yaparken IISExpress im kapalıydı. Bu nasıl olabiliyor. Core 2.2.0 üzerinde denediğimde hiçbir şekilde http isteklerime yanıt alamıyorum ayağa kaldırmak zorunda kalıyorum bu işlemi tamamlamak için.

    • Merhaba, aynı proje üzerinde mi denediniz? (Aynı NuGet ve framework versiyonları) Üzerinden uzun zaman geçti, yeni versiyonlarla deneme fırsatım olmadı maalesef. Dediğiniz gibi bir case ile karşılaşmamıştım daha önce çalışırken üzerinde.

    • Pact kütüphanesinin versiyon geçişlerinde ciddi "break changes" mevcut o yüzden paketleri güncellemek istediğinizde farklı hatalar ile karşılaşacaksınız. Ben de yeni versiyonunu güncelleyinceye kadar oldukça zaman harcadım.

      Hocam ayrıca bu değerli yazı için teşekkürler.

      • Teşekkür ederim Malik, aynen katılıyorum. Maintenance konusunda pek güzel anılarım yok Pact ile, ama kattığı değer, paha biçilmez. Eski çalışmış olduğum firmalarda aktif olarak kullanıyorduk.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

6 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 years ago