22 Temmuz 2019 Pazartesi

Yazılım Mimarisi - Hexagonal Architecture

Giriş
Bu mimari eski değil. Açıklaması şöyle
In 2005, Alistair Cockburn realized that there wasn’t much difference between how the user interface and the database interact with an application, since they are both external actors which are interchangeable with similar components that would, in equivalent ways, interact with an application. By seeing things this way, one could focus on keeping the application agnostic of these “external” actors, allowing them to interact via Ports and Adapters, thus avoiding entanglement and logic leakage between business logic and external components.
Hexagonal Architecture kavramını ilk olarak burada gördüm. Bu mimarinin ana gayesi şöyle. Yani uygulamamıza kolay bir şekilde bir başka arayüz takılması. Eğer uygulama konsoldan çalışıyorsa, kolayca Web arayüzü de takılabiliyor.
Hexagonal Architecture is a software architecture that allows an application to be equally driven by users, programs, automated tests, or batch scripts and to be developed in isolation from its run-time target system. The intent is to create an application to work without either a User Interface or a database so that we can run an automated regression test against the application, work with an application when a run-time system, such as a database is not available, or integrate applications without user interface.
Bu mimari bence en çok mikro servis geliştirirken işe yarıyor.

1. Yazılım Kısımları
Yazılımı iki kısma bölüyor.
1. Inside elements - Core Business Logic/Application Business Logic/Domain layer bulunur
2. Outside elements - DBs, external APIs, UI vesaire

- Kafka, Avro ve Spring-Boot kullanan bir örnek burada

Core Business Logic / Domain
Bu katmanda mümkün olduğunda herhangi bir altyapı, framework bağımlılığı olmamalı.

Örnek
Spring bağımlılığı olan ve olmayan kod şöyle. Bağımlılığı olmayan tercih edilmeli.
// Bad Practice
@Component
public class ExampleClass {
  @Autowired
  private SomeClass someClass;

  // some business code here
}
//Good Practice
public class ExampleClass {
  private final SomeClass someClass;

  public ExampleClass(SomeClass someClass) {
    this.someClass = someClass;
  }
 // some business code here
}

2. Port ve Adapter
Bu katmanlar arasında iletişim için port ve adapter kavramını kullanıyor. Şeklen şöyle

"Driving Side" inbound olarak düşünülebilir. "Driven Side" outbound olarak düşünülebilir. Açıklaması şöyle
Ports in hexagonal architecture refer to the interfaces that allow inbound or outbound flow. An inbound port exposes the core application functionality to the outside world. 
3. Port Nedir? - Inbound veya Outbound  Çalışan Arayüz
Açıklaması şöyle. Port arayüz anlamına gelir. Adapter ise Portu gerçekleştiren veya kullanan kod parçası anlamına gelir.
The connection between the inside and the outside part of our application is realized via abstractions called ports and their implementation counterparts called adapters. For this reason, this architectural style is often called Ports and Adapters.
Core Business Logic, sadece Port kavramını bilir. Port yani arayüz üzerinden okuma, yazma yapar. Adaptor ise Port arayüzünü gerçekleştirir. Şeklen şöyle. Burada Domain kodlarının sadece portları kullandığı görülebilir.

Port ve Adapter kolayca değiştirilebilir olmalı. Açıklaması şöyle.
We simply provide abstract ports and implement adapters to them, regardless of the type of actor the inside is communicating with. This means we can swap out the UI, the same way we swap out the database. We can easily swap out both for the testing purposes and there won’t be any significant implementation differences.
Port Örnekleri
Bazı port örnekleri şöyle
-userInterfacePort
-playerInputPort
-fooRepositoryPort

Hexagonal Architecture İle Repository yazısına bakabilirsiniz

userInterfacePort "Inside elements" içindeki bir servisi çağırabilir. Servisimiz de "Outside elements" içindeki bir repository portu çağırabilir.

Örnek
Elimizde iki tane Port olsun
//Dış dünyaya sağlanan servis
public interface PizzaService {
  public void createPizza(Pizza pizza);
  public Pizza getPizza(String name);
  public List<Pizza> laodPizza();
}

//Dış dünyanın sağladığı servis
public interface PizzaRepo {
  public void createPizza(Pizza pizza);
  public Pizza getPizza(String name);
  public List<Pizza> getAllPizza();
}

Eleştiri
Hexagonal gösterime okunaksız olduğu için eleştiri var. Katmanlı gösterimi okumak çok daha kolay deniyor. Şekle şöyle Aslında bu eleştiri okunabilirlik konusunda haklı. Ayrıca Domain paketlerini çok daha net gösterdiği için belki yine hakı


4. Adapter Nedir? - Portu Gerçekleştiren veya Kullanan Kod
Adapter ikiye ayrılabilir.

4.1. Primary Adapter - Dışarından Yapılan Sorgular
Inbound port olarak düşünülebilir. Uygulamanın çekirdeğine doğru yapılan çağrıdır. Burada Adapter nesnesi, Port arayüzünü kullanır. Core içinde Port arayüzünü gerçekleştiren bir "Application Service" bulunur. 
Örnek
Elimize şöyle bir adapter olsun. Burada adapter'in teknoloji bağımlığı olduğu görülebilir.
/**
 * Driving Adapter
 * Location: src/user-interface/adapter/OrderAdapter.ts
 */
class OrderAdapter extends HttpRequestHandler {

    private orderService: OrderService;

    constructor(orderService: OrderService) {
        super();
        this.orderService = orderService;
    }

    public async createOrder(req: Request): Promise<Response> {
        const createOrderCommand = new CreateOrderCommand(req);
        const orderResult = await this.orderService.handle(createOrderCommand);

        return this.createResponse(orderResult);
    }
}
Kullandığı port şöyle olsun. Kodda direkt Port arayüzü kullanılmıyor ama kullanılabilirdi de.
/**
 * Driving Port
 * Location: src/application/port/DrivingPort.ts
 */
interface DrivingPort {

    handle(command: OrderCommand): Promise<boolean>;
}

/**
 * Application service implementing Driving Port
 * Location: src/application/service/OrderService.ts
 */
class OrderService implements DrivingPort {

    private order: Order;
    private orderRepository: OrderRepositoryAdapter;

    constructor(orderRepository: DatabasePort) {
        this.orderRepository = orderRepository;
    }

    public async handle(command: CreateOrderCommand): Promise<boolean> {
        this.order = Order.create(command);

        try {
            return await this.orderRepository.save(this.order);
        } catch (error) {
            return false;
        }
    }
}

4.2. Secondary Adapter - Dışarıya Yapılan Çağrılar
Outbound port olarak düşünülebilir. Uygulamadan veri tabanına veya 3. taraf API'lere yapılan çağrıdır. Burada Adapter nesnesi, Port arayüzünü gerçekleştirir.

Örnek
İki tane String alan ve bunların anagram olup olmadığını dönen bir mikro servisimiz olsun. Bu servisi hem konsoldan hem de Rest çağrısı ile kullanmak isteyelim. Şöyle yaparız.
1. Domain Layer
IAnagramServicePort (Inbound Port) : Okuma işlemi için metod sunar.
AnagramService :  IAnagramServicePort'u gerçekleştirir. Bunu Spring servisi gibi düşünebiliriz

IAnagramMetricPort (Outbound Port) : Dışarıdaki respository'e metrikleri yazar.

2. Outside Layer
ConsoleAnagramAdaptor : IAnagramServicePort'u gerçekleştirir. Konsol arayüzü bu adaptörü kullanarak uygulamamızla etkileşir.

RestAnagramController : IAnagramServicePort'u gerçekleştirir. Http isteğini Java nesnelerine çevirir ve uygulamamızla etkileşir.. Yani Rest çağrılarına cevap verir.
5. Nerede Kullanılabilir?
Hexagonal Mimari en çok mikro servis mimarisindeki servisler yani daha küçük yazılım parçaları için kullanılıyor. Ancak kullanılması uygun olmayan yerler de var. Açıklaması şöyle
Hexagonal Architecture is not suitable for all situations. Like Domain-Driven Design, this is really applicable if you got a real business domain. For an application which transform a data to another format, that’s might be overkill.

6. Eksikler

6.1. Katmanları İç İçe Geçmesi
Hexagonal mimari BL ve UI katmanları arasında kullanılan DTO'lardan kaynaklanan katmanların iç içe geçmesi konusunda bir şey söylemiyor.

Aslında aynı problem Layered Architecture kullanımında da var. Port ve Adapter kolayca değiştirilebilir olsa da, bir yerde sonra Data Oriented veya Object Oriented mesajlaşma yaklaşımından birisi seçilmek zorunda kalınıyor. Bu seçim büyük ihtimalle Data Oriented DTO nesneleri oluyor. Dolayısıyla ekranda gösterilecek renkler/fontlar için yine her iki katman tarafından aynı şekilde yorumlanması gereken bilgiler saçılmaya başlıyor.

6.2. Core Business Logic İçinde Etkileşim
Core Business Logic içinde etkileşim, haberleşme en zor konulardan birisi. Bu doğru kurgulanmazsa Spagetti yapılar ortaya çıkıyor. Hexagonal mimari yine bu konuda bir öneri de bulunmuyor.


Hiç yorum yok:

Yorum Gönder