Trendyol Asistan’ı nasıl geliştirdik?

Gökhan Birinci
Trendyol Tech
Published in
5 min readJun 22, 2020

--

Öncelikle merhabalar, bugünkü yazımızda Trendyol’da yeni bir mimari ile replatform ettiğimiz Chat Bot’u yeni adıyla Trendyol Asistan’ı geliştirirken kullandığımız mimari ve teknolojilerden bahsedeceğim.

Trendyol Asistan temelde state machine mimarisi üzerine kurulmuş, kullanıcıların Müşteri Temsilcisine bağlanmadan sorunlarının çözümlerini bulabilmesi amacıyla tasarlanmış bir uygulama.

İşe öncelikle state machine’i biraz irdeledikten sonra başalayabiliriz.

State Machine nedir?

State machine’i doğrudan teorik bir anlatım ile değil bir örnek üzerinden küçük tanımlamalar ile anlatmak isterim:

Bir E-Ticaret sitesinden bir ürün aldığımızda ürünümüzün durumları temel olarak aşağıdaki şekilde olacaktır.

Bilgisayar bilimlerinde bu tür iş akışlarını modellemek için kullanılan çözüm yollarından birine de state machine (durum makinesi) denir.

State machineler davranışsal olarak her zaman tutarlı, stateler arası geçişlerin iyi tanımlandığı, anlamanın ve geliştirmenin kolay olduğu güçlü bir yöntem.

Tamam.. Ne demek şimdi stateler arası geçişlerin iyi tanımlandığı, anlamanın ve geliştirmenin kolay olduğu ?

State machinelerin kendisinden önceki ve sonraki durumların tutulabildiği, bir stateten başka bir state’e geçerken hangi metodun tetikleneceğinin belirlenip bu tanımlar ile başlatıldığı bir yapısı vardır. Bu yapı da sistemimizi kolay anlaşılabilir ve geliştirilebilir kılıyor.

State machine ile hayatımıza bir takım yeni kelimeler giriyor; state:(durum), transition(geçiş) ve event(olay).

State: Kurguladığımız sistemimizin o anki durumunu ifade etmek için kullandığımız kelime, örneğimiz üzerinden gidecek olursak bizim için her bir kutucuk bir state’e karşılık gelir.

Transition: Tam olarak kelime anlamında ki gibi stateler arası geçişlerde olan olaylara transition diyoruz. Sipariş Alındı state’inden Tedarik Edildi state’ine geçiş aşamasında ki olaya verilen isim transition olmuş oluyor.

Event: Stateler arası geçişi tetikleyen olay. Sipariş Al eventinin fırlatılıp stateler arası geçişin tetiklenmesi durumu. Burada transition ile event’in farkına değinmek gerekirse, transition geçiş yapılırken gerçekleşen olay, event ise o geçişi başlatmak için gereken komut.

Biz mimarisel olarak bu artıları göz önünde bulundurarak state machine kullanmaya karar verdik ve bunun sonucunda kullanılan frameworkleri inceledik ve Spring State Machine’i seçtik.

Spring State Machine

Spring state machine developerların state machine konseptlerini kullanabilmelerini sağlayan bir framework.

Bizim için avantajları:

  • Spring framework ile daha önce tecrübemizin olması,
  • Transitionların, guardların kullanım kolaylığı,
  • State Machine’i kalıcı bir bellekte saklayabilmemizi sağlaması ve bu sayede de Trendyol Asistan uygulamasını dağıtık bir şekilde çalıştırabiliyor olmamız.

Bu artıların ötesinde biz de kurguladığımız mimari için state machine üzerinde bazı geliştirmeler yaptık. Bu geliştirmeler sayesinde sistemimizdeki state’leri birbirilerinden kolay koparılabilir ve productionda birden çok versiyona aynı anda destek verebilir bir yapı haline getirdik.

Peki neler yaptık ?

Şimdi spring state machine’in gösterdiği örnekteki gibi bir sınıfı inceleyelim.

Spring state machine’de ilk olarak configure methodunda olduğu gibi tüm state’lerin tanımlaması veriliyor ve çok sayıda state olduğu zaman bu durum iyice karmaşık bir hale geliyor.

İkinci configure metodumuzda ise stateler arası geçişleri tetikleyecek komutların(event) tanımlarını bu geçiş ile birlikte neler yapılacağının(transition) ve geçişlerin yapılıp yapılamayacağının kontrolünün(guard: önceki durumlarımızdan ilerleyecek olursak tedarik edildi durumuna geçirirken depodaki ürünleri apisine call et stokta var ise devam et.) tanımları veriliyor.

Yukarıda ki bold şekilde yazılmış problemlerden yola çıkarak;

Belirtilen mimarisel bir takım problemlerin yanında bizim için bazı business problemleri vardı örneğin; bir çok state’ten gidilebilen bir state implement etmemiz gerekiyordu gibi.

O zaman gelin beraber belirttiğimiz problemlerin üzerinden geçerek örnek bir implementasyon yapalım.

Öncelikle state machine’i chatbot için kurgulamaya başlayalım;

Initial State başlıkları dönen state ve her bir başlık bizim için bir state gibi düşünerek başlayabiliriz.

Chatbotun küçük bir kısmını yukarıdaki şekilde ele alacak olursak bizim için her bir kutucuk bir statei temsil ediyor ve okların üzerinde olan yazılar ise stateler arası geçişi sağlayan eventleri.

Öncelikle standart bir Spring Boot Web projemizde şu dependency’ler olmalı :

<dependency>
<artifactId>spring-statemachine-core</artifactId>
<groupId>org.springframework.statemachine</groupId>
<version>${spring.statemachine}</version>
</dependency>
<dependency>
<artifactId>reflections</artifactId>
<groupId>org.reflections</groupId>
<version>${reflections}</version>
</dependency>
<spring.statemachine>2.1.3.RELEASE</spring.statemachine>
<reflections>0.9.9</reflections>

bunlar adından da anlaşılacağı üzere biri state-machine bağımlılığı diğeri de reflection kütüphanesi (uygulamamızda kullandığımız yere birazdan değineceğim).

Örneğimizde olan stateler üzerinden ilerleyecek olursak state tanımlarımızı aşağıdaki gibi yapıyoruz.

Tanımlamalardan önce SpringState Machine oluştururken Spring State tanımlarını ve Event tanımlarını String olarak tanımlamanıza olanak sağlıyor fakat biz type safe ilerlemek için o kısımları enum kullanmayı tercih ettik.

Event tanımlarımız ise şu şekilde olacak:

Biz projeyi implement ederken eventlerin önüne GET prefix’i getirerek kullanmayı tercih ettik.

StateMachine kullanırken başlarda da bahsettiğim gibi kocaman bir state machine tanımlarının olduğu class yapısındansa her bir state’in kendi business’ını tuttuğu state bilgisinin ve event bilgisinin üzerinde tanımlandığı bir yapı kurmak istedik bunun içinde kendimiz bir takım tasarım kararları aldık. Bunların başında her bir statein bir sınıfı olmalıydı ve bizim rulelarımızın olduğu State interfaceimizi implement etmeliydi.

İnterface tanımımız ise şu şekilde:

transitionsWrapper() metodu ile common state tanımlarını bütün statelere geçmiş oluyoruz.

Örnek olarak Initial State’in implementasyonuna bakalım:

Sınıfı incelediğimizde getTransitions() metodunda initial state üzerinden gidilebilecek stateleri ve ek olarak TransitionModel sınıfını ve @Statable anotasyonunu görüyoruz. O kısımlara birazdan StateMachine’i build ettiğimiz sınıfı anlatırken değineceğim.

State interfaceimiz sayesinde artık state olacak bütün sınıfların; transtionlarını, startAction ve diğer bilgilerini doldurması zorunluluğunu sağladık, aynı zamanda state sınıflarını kendi business’ını tutabileceği bir yapıya dönüştürmüş olduk.

Şimdi sıra geldi StateMachine’i build edeceğimiz sınıfı oluşturmaya, öncelikle oluşturacağımız sınıf EnumStateMachineConfigurerAdapter sınıfını extend etmeli ve @EnableStateMachine anotasyonu ile annotate edilmelidir.

görüldüğü üzere artık StateMachine konfigürasyon sınıfımız biraz daha sade ve anlaşılır olmaya başladı. Detaylara girmeden önce bu kısmı bu şekilde bırakalım ve yukarıdaki inject ettiğimiz transitionModels ,stateBeanNames fieldlarını nasıl doldurduğumuza değinelim, o kısım için de StatableConfig adında bir sınıf oluşturuyoruz ve şu şekilde implement ediyoruz:

transitionModels metodu içerisinde gidip @Statable anotasyonu ile annotate edilmiş sınıfları getirip transitionlarını beanimize dolduruyoruz ve stateBeanNames metodunda bütün state sınıflarının bean namelerini listeye dolduruyoruz.

Şimdi tekrar yukarıda ki StateMachine builder sınıfımıza geri dönelim ve metod metod inceleyelim.

bu metodda oluşturduğumuz statelerin içinde bulunan transitions metodlarındaki transition tanımlarını Spring’in state machine configurer sınıfına geçiyoruz, biz burada sourceState, targetState ve Event tanımlarını geçtik. O yüzden yukarıda da bahsettiğim TransitionModel sınıfımızı şu şekilde oluşturduk.

Şimdi gelelim diğer metoda:

burada bean isimlerini tuttuğumuz listedeki isimler ile Spring’in Application Context’inden statelerimizi alarak State Configurer’a startAction(), exitAction() metodlarını rahatlıkla geçmiş olduk, ek olarak bu örnekte kullanmadığımız guard özelliğini ileride kullanmak istediğimizde Parent State’ine guard metodunu ekleyip ilgili statelerde rule tanımlarını yapıp hayatımıza devam edebileceğiz.

Bu şekilde bir implementasyon ile bizim için yeni bir state eklemek sadece sınıfı oluşturup ilgili businessını implement etmek olacak.

Okuduğunuz için teşekkürler.

Bu arada, Trendyol’da teknoloji ekibimizi büyütmeye devam ediyoruz. Bu tür problemler ilginizi çekiyorsa, LinkedIn üzerinden başvuruda bulunabilirsiniz.

https://github.com/gokhanbirincii/customstatemachine

--

--