Architecture

Ports & Adapters Architecture

Trong bài viết trước chúng ta đã nói về Domain Driven Design – cái đặt nền móng cho các triết lý thiết kế trong kiến trúc hiện đại ngày nay, lấy domain làm trung tâm của ứng dụng. Tuy nhiên, Domain Driven Design vẫn còn đi theo lối mòn theo kiểu kiến trúc phân lớp (Layered Architecture). Nếu nhìn vào architecture diagram thì thấy rõ ràng Domain layer không phải nằm ở trung tâm mà lại nằm ở phía dưới cùng của ứng dụng. Trong bài viết này, chúng ta sẽ tìm hiểu về Port & Adapter Architecture, cái sẽ kế thừa ý chí từ Domain Driven Design, lấy Domain làm trung tâm và cô lập tất cả các thành phần khác ở phía bên ngoài.

Ports & Adapters Architecture (hay còn được gọi là Hexagonal Architecture, Kiến trúc lục giác) được Alistair Cockburn nghĩ ra và được viết ra trên blog của ông vào năm 2005. Đây là cách ông định nghĩa mục tiêu của nó trong một câu:

Cho phép application được thực thi, chèo lái (driven) bởi nhiều tác nhân khác nhau như user, những application khác, những bộ automated test hay batch scripts và được phát triển và kiểm thử độc lập, không phụ thuộc vào device (đầu vào / đầu ra) thật và database.

Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.

Alistair Cockburn 2005, Ports and Adapters

Tôi đã thấy một số bài báo nói về Ports & Adapters Architecture đã nói rất nhiều về các layers. Tuy nhiên, Alistair Cockburn, ông ấy không hề đề cập đến layers trong bài viết gốc của ông.

Ý tưởng là suy nghĩ về ứng dụng của chúng ta là trung tâm của một hệ thống, nơi mà tất cả các đầu vào và đầu ra đều đến và đi thông qua một cổng (port) – cái sẽ cô lập ứng dụng của chúng ta từ các công cụ, công nghệ và cơ chế phân phối (delivery mechanisms) bên ngoài. Ứng dụng không có kiến thức về những người / những gì đang gửi đầu vào hoặc nhận đầu ra của nó. Điều này nhằm cung cấp sự bảo vệ chống lại sự tiến triển của công nghệ và yêu cầu người dùng, cái có thể làm cho sản phẩm lỗi thời ngay sau khi chúng được phát triển.

Trong bài đăng này, tôi sẽ đi sâu vào các chủ đề sau:

Vấn đề của cách làm truyền thống

Cách tiếp cận truyền thống có thể sẽ mang lại cho chúng ta những vấn đề ở cả phía front-end và back-end.

Ở front-end, chúng ta sẽ có rò rỉ logic nghiệp vụ vào giao diện người dùng (tức là khi chúng ta đưa use-case logic vào trong controller hoặc view, làm cho nó không thể tái sử dụng được trong các màn hình giao diện người dùng khác) hoặc thậm chí là rò rỉ UI vào logic nghiệp vụ (tức là khi chúng ta tạo ra các phương thức trong các entity của chúng ta vì một số logic chúng ta cần trong một template).

hexagonal-arch-5-traditional2

Ở front-end, chúng ta có thể rò rỉ các thư viện và công nghệ bên ngoài vào trong logic kinh doanh bởi vì chúng ta có thể tham chiếu (reference) trực tiếp đến chúng thông qua type hinting, kế thừa, hoặc khởi tạo các lớp thư viện trong business logic của chúng ta.

Tiến hóa từ Layered Architecture

Đến năm 2005, nhờ EBI và DDD, chúng ta biết rằng những gì thực sự có liên quan trong hệ thống là các layer bên trong. Những layer này là nơi chứa tất cả các logic nghiệp vụ, chúng là sự khác biệt thực sự đối với các đối thủ cạnh tranh của chúng ta. Đó là “application” thật sự.

Nhưng Alistair Cockburn nhận ra rằng các layer trên cùng và dưới cùng, chỉ đơn giản là điểm đầu vào/đầu ra đến/từ ứng dụng. Mặc dù chúng thực

sự khác nhau nhưng chúng vẫn có những

hexagonal-arch-1-outer-layers-similarity

mục tiêu rất giống nhau và có sự đối xứng trong thiết kế. Hơn nữa, nếu chúng ta muốn cô lập các lớp ứng dụng bên trong của chúng ta, chúng ta có thể làm điều đó bằng cách sử dụng những điểm vào/ra theo một cách tương tự.

Để thoát khỏi sơ đồ phân lớp điển hình, chúng ta sẽ mô tả hai bên của hệ thống là trái và phải, thay vì trên và dưới cùng.

hexagonal-arch-2-left-right6

Mặc dù chúng ta có thể xác định hai mặt đối xứng của ứng dụng, mỗi bên có thể có một số điểm xuất nhập. Ví dụ, một API và giao diện người dùng là hai điểm xuất nhập khác nhau ở phía bên trái của ứng dụng, trong khi ORM và công cụ tìm kiếm là hai điểm xuất nhập khác nhau ở phía bên phải ứng dụng của chúng ta. Để chứng minh rằng ứng dụng của chúng ta có một số điểm xuất nhập, chúng ta sẽ vẽ sơ đồ ứng dụng của chúng ta với một vài mặt. Sơ đồ có thể có bất kỳ đa giác nào với nhiều mặt, nhưng sự lựa chọn đó là một hình lục giác. Do đó tên “Kiến trúc lục giác“.

hexagonal-arch-3-hexagon2

Kiến trúc Ports & Adapters giải quyết các vấn đề đã được xác định trước đó bằng cách sử dụng một lớp trừu tượng, được thực hiện như một port và môtk adapter.

Port là gì?

Như tên gọi của nó, một Port là một cổng đầu vào, đầu ra trong ứng dụng của chúng ta. Trong nhiều ngôn ngữ, nó sẽ là một interface.

Ví dụ: có thể là một interface được sử dụng để thực hiện tìm kiếm trong công cụ tìm kiếm. Trong ứng dụng của chúng ta, chúng ta sẽ sử dụng giao diện này mà không cần có kiến thức về việc thực hiện cụ thể là gì.

Adapter là gì?

Cũng như tên gọi của nó, Adapter (Bộ chuyển đổi) là một lớp chuyển đổi (thích nghi) một interface thành một interface khác.

Ví dụ, một adapter implement một interface A và được đưa vào interface B. Khi adapter được khởi tạo, nó được đưa trong constructor của đối tượng implement interface B. Adapter này sau đó được đưa vào bất cứ nơi nào interface A là cần thiết và nhận được phương pháp yêu cầu rằng nó chuyển đổi và proxy đến đối tượng bên trong thực hiện interface B.WWW

Nếu vẫn chưa hiểu, đừng lo lắng, lát nữa chúng ta sẽ xem xét các ví dụ cụ thể

Hai loại adapter khác nhau

Những Adapter ở phía bên trái, đại diện cho giao diện người dùng, được gọi là Adapter sơ cấp (primary) vì chúng là nơi để bắt đầu một số action đối với ứng dụng, trong khi những Adapter ở bên phải, đại diện cho các connection với các công cụ phụ trợ, được gọi là Adapter thứ cấp (Secondary) bởi vì chúng chỉ được gọi khi primary Adapter thực hiện một hành động nào đó.

  • Phía bên trái, Adapter phụ thuộc vào Port và được đưa (inject) vào trong implementation cụ thể của port (hay còn gọi là nơi thực hiện use-case). Cả Port và implementation cụ thể của nó (use-case) thuộc về bên trong ứng dụng;
  • Ở phía bên phải, Adapter là implementation cụ thể của Port và được đưa (inject) vào business logic của chúng ta, ứng dụng của chúng ta chỉ làm việc trên interface đó. Ở phía này, Port nằm trong ứng dụng, nhưng implementation của nó thuộc về bên ngoài và wrap một số tool bên ngoài.

hexagonal-arch-4-ports-adapters2

Lợi ích

Sử dụng port/adapter design, với ứng dụng của chúng ta nằm ở trung tâm của hệ thống, cho phép chúng ta giữ ứng dụng được tách biệt với các chi tiết triển khai như công nghệ thường xuyên thay đổi, công cụ và cơ chế phân phối, làm cho giai đoạn chứng minh khả năng (Proof of Concept – POC) và thử nghiệm sản phẩm (Pilot Phase) trở nên dễ dàng thực hiện hơn.

Tách biệt các hiện thực (Implementation) cụ thể và công nghệ

Bài toán

Chúng ta có một ứng dụng sử dụng SOLR làm search engine và sử dụng một open source library để kết nối với nó và thực hiện việc tìm kiếm.

Hướng tiếp cận truyền thống

Với phương pháp tiếp cận truyền thống, chúng ta sẽ sử dụng các lớp thư viện trực tiếp trong codebase của chúng ta bằng cách khởi tạo trực tiếp lớp thư viện đó hoặc tạo ra lớp kế thừa từ lớp thư viện đó.

Tiếp cận theo hướng Ports & adapters

Chúng ta sẽ tạo ra một interface, chúng ta hãy gọi nó là UserSearchInterface, và sử dụng trong code của chúng ta. Chúng ta cũng sẽ tạo một adapter cho SOLR, sẽ implement UserSearchInterface với tên là UserSearchSolrAdapter. Đây là một wrapper cho thư viện SOLR, do đó nó sẽ được inject vào hệ thống của chúng ta, hệ thống không hề biết sự tồn tại cụ thể của UserSearchSolrAdapter, mà chỉ work trên UserSearchInterface.

Vấn đề

Tại một số thời điểm, chúng ta muốn chuyển từ SOLR sang Elasticsearch. Hơn nữa, đối với cùng một tìm kiếm, đôi khi chúng ta muốn sử dụng SOLR và những lần khác chúng ta muốn sử dụng Elasticsearch, tất cả phụ thuộc vào lúc run-time.

Nếu chúng ta sử dụng cách tiếp cận truyền thống, chúng ta sẽ phải tìm kiếm và thay thế việc sử dụng thư viện SOLR cho thư viện Elasticsearch. Tuy nhiên, đó không đơn giản chỉ là tìm và thay thế: các thư viện có những cách khác nhau được sử dụng, các phương pháp khác nhau với đầu vào và đầu ra khác nhau, do đó thay thế các thư viện sẽ không phải là một nhiệm vụ tầm thường. Và việc sử dụng một thư viện thay vì một thư viện khác lúc run-time, thậm chí sẽ không thể thực hiện được.

Tuy nhiên, nếu chúng ta sử dụng Ports & Adapters, chúng ta chỉ cần tạo một adapter mới, đặt tên nó là UserSearchElasticsearchAdapter và dùng nó thay vì adapter SOLR, có thể chỉ bằng cách thay đổi cấu hình trong DIC. Để thực hiện các thao tác khác nhau lúc run-time, chúng ta có thể sử dụng Factory để quyết định nên dùng adapter nào.

Tách biệt cơ chế phân phối đầu ra (Delivery mechanisms isolation)

Tương tự như ví dụ trước, giả sử chúng ta có một ứng dụng cần một GUI web, một CLI và một API web. Chúng ta cũng có một số chức năng mà chúng ta muốn cung cấp trong cả ba giao diện người dùng, hãy gọi chức năng UserProfileUpdate đó.

Sử dụng Ports & Adapters, chúng ta sẽ thực hiện chức năng này trong một application service method và nghĩ nó như là một use-case. Service này sẽ implement một interface xác định các phương pháp, đầu vào và đầu ra.

Mỗi phiên bản UI sẽ có một Controller (hoặc console command) mà có thể sử dụng giao diện đó để kích hoạt logic mong muốn. Ở đây, Adapter thực sự là controller (hoặc CLI Command).

Sau đó, chúng ta có thể thay đổi hoàn toàn UI mà không ảnh hưởng đến business logic.

Testing

Trong cả hai ví dụ trước, testing trở nên dễ dàng hơn với Kiến trúc Ports and Adapters. Trong những ví dụ đầu tiên, chúng ta có thể mô phỏng hoặc khai báo giao diện (Port) và test ứng dụng của chúng ta mà không cần sử dụng SOLR cũng như Elasticsearch.

Trong ví dụ thứ hai, chúng ta có thể test tất cả các UI trong sự cô lập từ ứng dụng, và ngược lại, các use-case (service) sẽ được test mà không cần UI, chỉ cần đưa vào các dạng input thô và verify output thô.

Kết luận

Theo cách tôi nhìn thấy, kiến trúc Ports & Adapters chỉ có một mục đích: cô lập business logic từ các cơ chế phân phối và các công cụ được hệ thống sử dụng. Bằng cách sử dụng interface trong lập trình.

Ở phía UI (các driving adapter), chúng ta tạo ra adapters sử dụng các application interfaces của chúng ta, ví dụ như Controller.

Về phía cơ sở hạ tầng (các driven adapter), chúng ta tạo ra các adapter implement các interface từ ứng dụng của chúng ta, ví dụ như Repository.

Đó là tất cả!

Tuy nhiên, lưu ý rằng ý tưởng này cũng đã được công bố 13 năm trước, mặc dù không rõ ràng nhấn mạnh mục tiêu cô lập các công cụ và cơ chế phân phối từ cốt lõi của ứng dụng.

fig_7_14_boundaries
Ivar Jacobson 1992, pp. 171

Bất kỳ interactor nào của hệ thống với một Actor đi qua một đối tượng Boundary. Như Jacobson mô tả, một Actor có thể là một người sử dụng giống như một khách hàng hoặc một quản trị viên (nhà điều hành), nhưng nó cũng có thể là một “người sử dụng” không phải là con người giống như một máy báo động hoặc một máy in, cái tương ứng với Driving Adapters và Driven Adapters của Ports & Adapters Architecture

Bài viết được tham khảo từ:

Ports & Adapters Architecture

Đọc thêm:

1992 – Ivar Jacobson – Object-Oriented Software Engineering: A use case driven approach

200? – Alistair Cockburn – Hexagonal Architecture

2005 – Alistair Cockburn – Ports and Adapters

2012 – Benjamin Eberlei – OOP Business Applications: Entity, Boundary, Interactor

2014 – Fideloper – Hexagonal Architecture

2014 – Philip Brown – What is Hexagonal Architecture?

2014 – Jan Stenberg – Exploring the Hexagonal Architecture

2017 – Grzegorz Ziemoński – Hexagonal Architecture Is Powerful

2017 – Shamik Mitra – Hello, Hexagonal Architecture

Tổng hợp bởi edwardthienhoang

4 thoughts on “Ports & Adapters Architecture”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.