Phần 2: Phá vỡ hệ thống cũ để xây dựng backend ở SoundCloud

Đội kỹ sư ở SoundCloud

Ở phần I, chúng ta đã cùng tìm hiểu về việc đội kỹ sư ở SoundCloud xây dựng các microservice với ngôn ngữ Scala, Clojure, và Ruby mà không có sự gắn kết chặt chẽ với hệ thống cũ của họ dựa trên Rails. Sau đó việc thay đổi kiến trúc như vậy làm cho họ có thể tự do xây dựng những tính năng mới và nhiều cải tiến rất linh hoạt. Một câu hỏi khá quan trọng là: Làm thế nào để họ tách những tính năng từ một cục nguyên khối viết trên Rails hay họ gọi là “Tàu Mẹ”?

Tách một hệ thống cũ ra không phải một chuyện dễ dàng, nhưng may mắn là chúng ta có rất nhiều open-source và các công cụ hỗ trợ dể làm chuyện đấy.

Bước đầu tiền cần làm đó là xác định cũng thành phần mà chúng ta cần tách ra. Ở SoundCloud, họ quyêt định sử dụng phong cách thiết kế theo hướng Bounded Context. Một ví dụ rõ ràng về Bounded Context là chức năng nhắn tin giữa những người dùng trên SoundCloud, đây là một thành phần toàn mà bộ tính năng của nó có tính gắn kết cao và không liên quan nhiều đến phần còn lại của hệ thống, và nó chỉ nắm giữ các liên kết yếu đến phần còn lại của dữ liệu.

Sau khi đã xác định các Bounded Context, nhiệm vụ kế tiếp là tìm cách tách chúng ra khỏi “Tàu Mẹ”. Không may thay Rails ActiveRecord framework dẫn đắt cách thiết kế kiến trúc rất gắn kết. Một đoạn code mẫu theo cách thiết kế đấy như sau:

def index
  if (InboxItem === item)
    respond mailbox_items_in_collection.index.paginate(:page => params[:page])
  else
    respond mailbox_items_in_collection.paginate(
      :joins => "INNER JOIN messages ON #{safe_collection}_items.message_id = messages.id",
      :page  => params[:page],
      :order => 'messages.created_at DESC')
  end
end

Bởi vị họ muốn tách chức năng nhắn tin như một Bounded Context ra một microservice, họ cần phải làm cho code trên trở nên linh động hơn. Bước đầu tiên cần phải làm cấu trúc lại code theo cách như này (bạn có thể tham khảo thêm về tư tưởng xử lý các thành phần lỗi thời ở đây: Working Effectively with Legacy Code)

def index
  conversations = cursor_for do |cursor|
    conversations_service.conversations_for(
    current_user,
    cursor[:offset],
    cursor[:limit])
  end

  respond collection_for(conversations, :conversations)
end

Phiên bản đầu tiên của phương thức conversations_service#conversations_for không có sự khác biệt với bản ở trên là bao; chức năng của nó tương tự như những gì làm trên ActiveRecord.

Họ đã sẵn sàng để tách phần chức năng này ra thành một microservice mà không cần có nhiều sự thay đổi ở tầng Controller và Presentation. Đầu tiên họ thực hiên thay thế phần conversations_service#conversations_forbằng cách gọi service thống qua http request:

def conversations_for(user, offset = 0, limit = 50)
  response = @http_client.do_get(service_path(user), pagination(offset, limit))
  parse_response(user, response)
end

Họ cố gắn tránh sự thay đổi quá lớn nhiều nhất có thể , và với yêu cầu đấy học buộc phải để các microservices làm việc với “Tàu Mẹ” trong một khoản thời gian dài và trong khi đó họ sẽ tranh thử để mang logic ra ngoài các microservice mới.

Như có mô tả trước đây họ không muốn sử dụng cơ sở dữ liệu của “Tàu Mẹ” như là nơi các microservice tương tác với hệ thống củ. Họ sẽ tổ chức cơ sở dữ liệu như là một ứng dụng và xây dựng các service để tích hợp và điều đấy buộc họ phải tìm cách đồng bộ dữ liệu giữa các microservice sử dụng nó.
Mặc dù dự định là như vậy nhưng mà họ vẫn phải sử dụng cơ sơ dữ liệu từ “Tàu Mẹ” trong giai đoạn chuyển tiếp.

Điều này mang lại hai vấn đề khá quan trọng. Trong khi giai đoạn chuyển tiếp hoàn tất, các microservice mới không thể đổi cấu trúc của các bảng trong MySQL, thậm chí tệ hơn là phải sử dụng một hệ thống lưu trữ mới. Một ví dụ cho trường hợp này đó là hẹ thống nhắn tin từ người dùng này đến người dùng khác, nó được xây dựng dưa trên mô hình thread-based và được thay thế bằng một cái khác, họ phải có những crobjobs để giữa cho 2 database dược đồng bộ.

Một vấn đề khác liên quan đến hệ thống Semantic Events được mô tả ở phần I. Các hệ thống của họ được thiết kế để khi có sự thay đổi trên dữ liệu (ví dụ 1 người dùng comment vào một bài nhạc nào đấy) thì sẽ phát ra các events đến các microservice liên quan, hệ thống hiện tại chỉ cho phép event được phát ra từ một hệ thống, bởi vì họ không thể để cả “Tàu Mẹ” và các microservice mới cùng phát ra những event, và vì thế họ đã chỉ chuyển đổi sang hệ thống microservice mới khi mà họ đã hoàn tất các tính năng như của “Tàu Mẹ”. Với chiến lược này họ đã gặp ít vấn đề hơn họ nghĩ, tuy nhiên vì ưu tiên làm sao để ít có tác động tới hệ thống đang chạy nên việc chuyển đổi bị hạn chế, không diễn ra nhanh chóng được.

Bằng cách sử dụng cách này họ đã chuyển hầu hết các chức năng của “Tàu Mẹ” ra các microservices. Hiện tại họ đã xây dựng hệ thống nhắn tin giữa người dùng hoàn toàn độc lập với hệ thống củ (bạn có thể tham khảo ở đây: link)

Ở phần tiếp theo chúng ta sẽ cùng theo dõi họ đã sử dụng Scala & Finagle như thế nào để xây dựng các microservice..