JavaRush /Blog Java /Random-VI /Hướng dẫn về Java Microservices. Phần 1: Kiến trúc và kiế...

Hướng dẫn về Java Microservices. Phần 1: Kiến trúc và kiến trúc cơ bản về microservice

Xuất bản trong nhóm
Trong hướng dẫn này, bạn sẽ tìm hiểu microservices Java là gì, cách thiết kế và tạo chúng. Nó cũng bao gồm các câu hỏi về thư viện microservice Java và tính khả thi của việc sử dụng microservice. Dịch và điều chỉnh các vi dịch vụ Java: Hướng dẫn thực hành .

Vi dịch vụ Java: Cơ bản

Để hiểu microservice, trước tiên bạn phải xác định chúng không phải là gì. Nó không phải là “monolith” - Java monolith: nó là gì và ưu điểm hay nhược điểm của nó là gì? Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 1

Một khối Java là gì?

Hãy tưởng tượng bạn làm việc cho một ngân hàng hoặc một công ty khởi nghiệp về công nghệ tài chính. Bạn cung cấp cho người dùng một ứng dụng di động mà họ có thể sử dụng để mở tài khoản ngân hàng mới. Trong mã Java, điều này sẽ dẫn đến sự hiện diện của một lớp trình điều khiển. Đơn giản hóa, nó trông như thế này:
@Controller
class BankController {

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        riskCheck(form);
        openBankAccount(form);
        // etc..
    }
}
Bạn cần bộ điều khiển để:
  1. Đã xác nhận mẫu đăng ký.
  2. Đã kiểm tra rủi ro về địa chỉ của người dùng để quyết định có cung cấp tài khoản ngân hàng cho anh ta hay không.
  3. Đã mở tài khoản ngân hàng.
Lớp này BankControllersẽ được đóng gói cùng với các nguồn còn lại của bạn thành tệp Bank.jar hoặc Bank.war để triển khai - đây là một khối nguyên khối cũ tốt chứa tất cả mã cần thiết để chạy ngân hàng của bạn. Theo ước tính sơ bộ, kích thước ban đầu của tệp .jar (hoặc .war) sẽ nằm trong khoảng từ 1 đến 100 MB. Bây giờ bạn có thể chỉ cần chạy tệp .jar trên máy chủ của mình... và đó là tất cả những gì bạn cần làm để triển khai ứng dụng Java của mình. Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 2Hình ảnh, hình chữ nhật trên cùng bên trái: triển khai ngân hàng mono(lithic) java -jarbank.jar (cp .war/.ear vào máy chủ ứng dụng). Hình chữ nhật bên phải: mở trình duyệt.

Vấn đề với nguyên khối Java là gì?

Vốn dĩ không có gì sai với các khối nguyên khối của Java. Tuy nhiên, kinh nghiệm đã chỉ ra rằng nếu trong dự án của bạn:
  • Có rất nhiều lập trình viên/nhóm/tư vấn viên đang làm việc...
  • ...trên cùng một khối nguyên khối dưới áp lực từ khách hàng với những yêu cầu rất mơ hồ...
  • trong vòng vài năm nữa...
... thì trong trường hợp này, chỉ riêng tệp ngân hàng.jar nhỏ của bạn sẽ biến thành một gigabyte mã khổng lồ, điều này thật đáng sợ khi tiếp cận chứ chưa nói đến việc triển khai.

Làm cách nào để giảm kích thước của một khối Java?

Một câu hỏi tự nhiên được đặt ra: làm thế nào để làm cho tảng đá nguyên khối nhỏ hơn? Hiện tại, ngân hàng.jar của bạn đang chạy trên một JVM, một quy trình trên một máy chủ. Không nhiều không ít. Và ngay bây giờ một ý nghĩ hợp lý có thể xuất hiện trong đầu bạn: “Nhưng dịch vụ xác minh rủi ro có thể được các bộ phận khác trong công ty của tôi sử dụng! Nó không liên quan trực tiếp đến ứng dụng ngân hàng nguyên khối của tôi! Có lẽ nó nên được cắt ra khỏi nguyên khối và triển khai như một sản phẩm riêng biệt? Nghĩa là, về mặt kỹ thuật, chạy nó như một quy trình Java riêng biệt.”

Dịch vụ vi mô Java là gì?

Trong thực tế, cụm từ này có nghĩa là bây giờ lệnh gọi phương thức riskCheck()sẽ không được thực hiện từ BankController: phương thức này hoặc thành phần Bean cùng với tất cả các lớp phụ trợ của nó sẽ được chuyển sang dự án Maven hoặc Gradle của chính nó. Nó cũng sẽ được triển khai và đặt dưới sự kiểm soát phiên bản độc lập với khối ngân hàng nguyên khối. Tuy nhiên, toàn bộ quá trình trích xuất này không biến mô-đun RiskCheck mới của bạn thành một vi dịch vụ thực sự vì định nghĩa về vi dịch vụ có thể được giải thích. Điều này dẫn đến các cuộc thảo luận thường xuyên trong các nhóm và công ty.
  • Có phải 5-7 lớp trong một dự án vi mô hay không?
  • 100 hay 1000 lớp... vẫn còn vi mô?
  • Microservice có liên quan chung đến số lượng lớp học hay không?
Hãy bỏ lại lý luận mang tính lý thuyết mà thay vào đó hãy tập trung vào những cân nhắc thực tế và thực hiện điều này:
  1. Hãy gọi tất cả các dịch vụ được triển khai riêng biệt là microservice, bất kể quy mô hoặc ranh giới miền của chúng.
  2. Hãy suy nghĩ về cách sắp xếp liên lạc giữa các dịch vụ. Các vi dịch vụ của chúng ta cần có cách để giao tiếp với nhau.
Vì vậy, tóm lại: trước đây bạn đã có một quy trình JVM, một khối nguyên khối vững chắc để điều hành ngân hàng. Bây giờ bạn có một quy trình JVM nguyên khối dành cho ngân hàng và một vi dịch vụ RiskCheck riêng biệt chạy trong quy trình JVM của chính nó. Và bây giờ, để kiểm tra rủi ro, khối nguyên khối của bạn phải gọi microservice này. làm như thế nào?

Làm cách nào để thiết lập liên lạc giữa các dịch vụ vi mô Java?

Nói chung và nói chung, có hai lựa chọn - giao tiếp đồng bộ và không đồng bộ.

Giao tiếp đồng bộ: (HTTP)/REST

Thông thường, giao tiếp được đồng bộ hóa giữa các vi dịch vụ diễn ra thông qua các dịch vụ giống như HTTP và REST trả về XML hoặc JSON. Tất nhiên, có thể có các tùy chọn khác - ít nhất hãy lấy Bộ đệm giao thức của Google . Nếu bạn cần phản hồi ngay lập tức, tốt hơn nên sử dụng giao tiếp REST. Trong ví dụ của chúng tôi, đây chính xác là những gì cần phải làm vì cần phải xác minh rủi ro trước khi mở tài khoản. Nếu không có kiểm tra rủi ro thì không có tài khoản. Chúng ta sẽ thảo luận về các công cụ bên dưới, trong phần “ Thư viện nào tốt nhất cho lệnh gọi Java REST đồng bộ ”.

Nhắn tin - Giao tiếp không đồng bộ

Giao tiếp microservice không đồng bộ thường được thực hiện bằng cách trao đổi tin nhắn với triển khai JMS và/hoặc sử dụng giao thức như AMQP . Chúng tôi viết “thường” ở đây vì một lý do: giả sử không thể đánh giá thấp số lượng tích hợp email/SMTP. Hãy sử dụng nó khi bạn không cần phản hồi ngay lập tức. Ví dụ: người dùng nhấp vào nút “mua ngay” và đến lượt bạn, bạn muốn tạo hóa đơn. Quá trình này chắc chắn không nên xảy ra trong chu kỳ phản hồi yêu cầu mua hàng của người dùng. Dưới đây chúng tôi sẽ mô tả những công cụ nào là tốt nhất cho việc nhắn tin Java không đồng bộ .

Ví dụ: Lệnh gọi API REST trong Java

Giả sử chúng ta chọn giao tiếp microservice đồng bộ. Trong trường hợp này, mã Java của chúng tôi (mã chúng tôi đã trình bày ở trên) ở cấp độ thấp sẽ trông giống như thế này. (ở mức độ thấp ở đây, chúng tôi muốn nói đến thực tế là đối với giao tiếp vi dịch vụ, các thư viện máy khách thường được tạo để giúp bạn tránh xa các lệnh gọi HTTP thực tế).
@Controller
class BankController {

    @Autowired
    private HttpClient httpClient;

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        httpClient.send(riskRequest, responseHandler());
        setupAccount(form);
        // etc..
    }
}
Dựa trên mã, có thể thấy rõ rằng bây giờ chúng ta cần triển khai hai dịch vụ Java (vi mô) là Bank và RiskCheck. Kết quả là chúng ta sẽ có hai tiến trình JVM đang chạy. Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 3Đó là tất cả những gì bạn cần để phát triển một dự án vi dịch vụ Java: chỉ cần xây dựng và triển khai các phần nhỏ hơn (tệp .jar hoặc .war) thay vì một khối nguyên khối. Câu trả lời cho câu hỏi vẫn chưa rõ ràng: chúng ta nên cắt nguyên khối thành microservice như thế nào? Những mảnh này nên nhỏ đến mức nào, làm thế nào để xác định kích thước chính xác? Hãy kiểm tra.

Kiến trúc vi dịch vụ Java

Trên thực tế, các công ty phát triển các dự án microservice theo nhiều cách khác nhau. Cách tiếp cận này phụ thuộc vào việc bạn đang cố gắng chuyển đổi một khối nguyên khối hiện có thành một dự án vi dịch vụ hay bắt đầu dự án từ đầu.

Từ nguyên khối đến microservice

Một trong những ý tưởng hợp lý nhất là trích xuất các dịch vụ vi mô từ một khối nguyên khối hiện có. Lưu ý rằng tiền tố "micro" ở đây không thực sự có nghĩa là các dịch vụ được trích xuất sẽ thực sự nhỏ; điều này không nhất thiết phải như vậy. Chúng ta hãy nhìn vào nền tảng lý thuyết.

Ý tưởng: chia khối thành microservice

Cách tiếp cận microservice có thể được áp dụng cho các dự án cũ. Và đó là lý do tại sao:
  1. Thông thường, những dự án như vậy rất khó duy trì/thay đổi/mở rộng.
  2. Mọi người, từ nhà phát triển đến quản lý, đều muốn sự đơn giản hóa.
  3. Bạn có (tương đối) ranh giới tên miền rõ ràng, nghĩa là bạn biết chính xác phần mềm của mình nên làm gì.
Quay lại ví dụ của chúng tôi, điều này có nghĩa là bạn có thể xem xét khối ngân hàng Java nguyên khối của mình và cố gắng chia nhỏ nó theo các ranh giới miền.
  • Vì vậy, sẽ hợp lý nếu tách việc xử lý dữ liệu người dùng (như tên, địa chỉ, số điện thoại) thành một microservice riêng “Quản lý tài khoản”.
  • Hoặc "Mô-đun kiểm tra rủi ro" đã nói ở trên giúp kiểm tra mức độ rủi ro của người dùng và có thể được sử dụng bởi nhiều dự án khác hoặc thậm chí các bộ phận của công ty.
  • Hoặc mô-đun lập hóa đơn gửi hóa đơn ở định dạng PDF hoặc qua thư.

Thực hiện ý tưởng: để người khác thực hiện

Cách tiếp cận được mô tả ở trên trông tuyệt vời trên giấy và các sơ đồ giống UML. Tuy nhiên, mọi thứ không đơn giản như vậy. Việc triển khai thực tế của nó đòi hỏi phải có sự chuẩn bị kỹ thuật nghiêm túc: khoảng cách giữa sự hiểu biết của chúng ta về những gì sẽ tốt khi trích xuất từ ​​​​một tảng đá nguyên khối và bản thân quá trình trích xuất là rất lớn. Hầu hết các dự án doanh nghiệp đều đạt đến giai đoạn mà các nhà phát triển ngại nâng cấp phiên bản Hibernate 7 năm tuổi lên phiên bản mới hơn. Các thư viện sẽ được cập nhật cùng với nó, nhưng có nguy cơ thực sự phá vỡ thứ gì đó. Vì vậy, những nhà phát triển đó giờ đây phải tìm hiểu mã kế thừa cổ xưa với ranh giới giao dịch cơ sở dữ liệu không rõ ràng và trích xuất các dịch vụ vi mô được xác định rõ ràng? Thông thường, vấn đề này rất phức tạp và không thể “giải quyết” được trên bảng trắng hoặc trong các cuộc họp kiến ​​trúc. Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 4Trích lời nhà phát triển Twitter @simon brown: Tôi sẽ nói đi nói lại điều này... nếu mọi người không thể xây dựng nguyên khối một cách chính xác thì microservice sẽ không giúp ích được gì. Simon Brown

Dự án từ đầu dựa trên kiến ​​trúc microservice

Đối với các dự án Java mới, ba mục được đánh số từ phần trước trông hơi khác một chút:
  1. Bạn bắt đầu với một bảng xếp hạng sạch sẽ, do đó không có “hành lý” nào để duy trì.
  2. Các nhà phát triển muốn giữ mọi thứ đơn giản trong tương lai.
  3. Vấn đề: Bạn có một bức tranh mờ nhạt hơn nhiều về ranh giới miền: bạn không biết phần mềm của mình thực sự phải làm gì (gợi ý: linh hoạt ;))
Điều này dẫn đến việc các công ty phải thử nghiệm các dự án mới với các dịch vụ vi mô Java.

Kiến trúc microservice kỹ thuật

Điểm đầu tiên có vẻ là rõ ràng nhất đối với các nhà phát triển, nhưng cũng có những người cực lực phản đối nó. Hadi Hariri khuyến nghị tái cấu trúc "Trích xuất microservice" trong IntelliJ. Và mặc dù ví dụ sau đây rất đơn giản, nhưng thật không may, việc triển khai được quan sát trong các dự án thực tế lại không đi quá xa so với nó. Trước microservice
@Service
class UserService {

    public void register(User user) {
        String email = user.getEmail();
        String username =  email.substring(0, email.indexOf("@"));
        // ...
    }
}
Với microservice Java chuỗi con
@Service
class UserService {

    @Autowired
    private HttpClient client;

    public void register(User user) {
        String email = user.getEmail();
        //теперь вызываем substring microservice via http
        String username =  httpClient.send(substringRequest(email), responseHandler());
        // ...
    }
}
Vì vậy, về cơ bản bạn đang gói một lệnh gọi phương thức Java trong một lệnh gọi HTTP mà không có lý do rõ ràng nào để làm như vậy. Tuy nhiên, một trong những lý do là: thiếu kinh nghiệm và cố gắng áp dụng cách tiếp cận dịch vụ vi mô Java. Khuyến nghị: Đừng làm điều này.

Kiến trúc microservice định hướng quy trình làm việc

Cách tiếp cận phổ biến tiếp theo là chia các dịch vụ vi mô Java thành các mô-đun dựa trên quy trình công việc. Ví dụ thực tế: ở Đức, khi bạn đến gặp bác sĩ (công), ông ấy phải ghi lại chuyến thăm của bạn vào hệ thống CRM y tế của mình. Để nhận thanh toán từ bảo hiểm, anh ta sẽ gửi dữ liệu về việc điều trị của bạn (và việc điều trị cho những bệnh nhân khác) cho người trung gian thông qua XML. Nhà môi giới sẽ xem xét tệp XML này và (được đơn giản hóa):
  1. Sẽ kiểm tra xem có nhận được tệp XML chính xác hay không.
  2. Nó sẽ kiểm tra tính hợp lý của các quy trình: chẳng hạn, một đứa trẻ một tuổi nhận được ba quy trình làm sạch răng trong một ngày từ bác sĩ phụ khoa trông có vẻ hơi nghi ngờ.
  3. Sẽ kết hợp XML với một số dữ liệu quan liêu khác.
  4. Sẽ chuyển tiếp tệp XML đến công ty bảo hiểm để bắt đầu thanh toán.
  5. Và nó sẽ gửi kết quả đến bác sĩ, cung cấp cho anh ta thông báo “thành công” hoặc “vui lòng gửi lại bản ghi này ngay khi thấy có ý nghĩa”.
Ghi chú. Trong ví dụ này, giao tiếp giữa các vi dịch vụ không đóng vai trò gì nhưng cũng có thể được thực hiện không đồng bộ bởi một nhà môi giới tin nhắn (ví dụ: RabbitMQ), vì dù sao thì bác sĩ cũng không nhận được phản hồi ngay lập tức. Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 5Một lần nữa, điều này có vẻ tuyệt vời trên giấy tờ, nhưng các câu hỏi tự nhiên lại nảy sinh:
  • Có cần thiết phải triển khai sáu ứng dụng để xử lý một tệp XML không?
  • Những microservice này có thực sự độc lập với nhau không? Chúng có thể được triển khai độc lập với nhau không? Với các phiên bản và sơ đồ API khác nhau?
  • Microservice đáng tin cậy sẽ làm gì nếu microservice xác minh không hoạt động? Hệ thống vẫn hoạt động chứ?
  • Các vi dịch vụ này có chia sẻ cùng một cơ sở dữ liệu không (chúng chắc chắn cần một số dữ liệu chung trong bảng DB) hay mỗi vi dịch vụ đều có dữ liệu riêng?
  • … và nhiều hơn nữa.
Điều thú vị là sơ đồ trên trông đơn giản hơn vì mỗi dịch vụ hiện có mục đích chính xác và được xác định rõ ràng. Nó từng trông giống như khối đá nguyên khối đáng sợ này: Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 6Mặc dù bạn có thể tranh luận về tính đơn giản của những sơ đồ này nhưng giờ đây bạn chắc chắn cần phải giải quyết những thách thức vận hành bổ sung này.
  • Bạn không chỉ cần triển khai một ứng dụng mà ít nhất là sáu ứng dụng.
  • Bạn thậm chí có thể cần triển khai nhiều cơ sở dữ liệu, tùy thuộc vào mức độ bạn muốn đi sâu vào kiến ​​trúc vi dịch vụ.
  • Bạn cần đảm bảo rằng mỗi hệ thống đều trực tuyến và hoạt động bình thường.
  • Bạn cần đảm bảo rằng các lệnh gọi giữa các vi dịch vụ của bạn thực sự có khả năng linh hoạt (xem Cách làm cho một vi dịch vụ Java có khả năng linh hoạt?).
  • Và mọi thứ khác mà thiết lập này ngụ ý - từ cài đặt phát triển cục bộ đến thử nghiệm tích hợp.
Vì vậy, khuyến nghị sẽ là:
  • Nếu bạn không phải Netflix (rất có thể bạn không phải Netflix)...
  • Trừ khi bạn có kỹ năng làm việc siêu mạnh khi bạn mở môi trường phát triển và nó gọi một con khỉ hỗn loạn đến vứt bỏ cơ sở dữ liệu sản xuất của bạn, cơ sở dữ liệu này có thể dễ dàng khôi phục sau 5 giây.
  • hoặc bạn cảm thấy giống như @monzo và sẵn sàng dùng thử 1500 dịch vụ vi mô chỉ vì bạn có thể.
→ Đừng làm điều này. Và bây giờ nó ít cường điệu hơn. Việc cố gắng mô hình hóa các dịch vụ vi mô xuyên ranh giới miền có vẻ khá hợp lý. Nhưng điều này không có nghĩa là bạn cần thực hiện một quy trình công việc và chia nó thành các phần riêng lẻ nhỏ (nhận XML, xác thực XML, chuyển tiếp XML). Do đó, bất cứ khi nào bạn bắt đầu một dự án mới với các vi dịch vụ Java và ranh giới miền vẫn còn rất mơ hồ, hãy cố gắng giữ kích thước của các vi dịch vụ của bạn ở mức thấp. Bạn luôn có thể thêm nhiều mô-đun hơn sau này. Và hãy đảm bảo rằng bạn có DevOps nâng cao trong nhóm/công ty/bộ phận để hỗ trợ cơ sở hạ tầng mới của mình.

Kiến trúc microservice đa ngôn ngữ hoặc theo định hướng nhóm

Có một cách tiếp cận thứ ba, gần như theo chủ nghĩa tự do, để phát triển microservice: cho phép các nhóm hoặc thậm chí cá nhân triển khai câu chuyện của người dùng bằng bất kỳ ngôn ngữ hoặc microservice nào (các nhà tiếp thị gọi cách tiếp cận này là “lập trình đa ngôn ngữ”). Do đó, dịch vụ xác thực XML được mô tả ở trên có thể được viết bằng Java, trong khi vi dịch vụ xác thực cùng lúc có thể được viết bằng Haskell (để làm cho nó phù hợp về mặt toán học). Đối với microservice chuyển tiếp bảo hiểm, bạn có thể sử dụng Erlang (vì nó thực sự cần mở rộng quy mô;)). Trên thực tế, điều có vẻ thú vị theo quan điểm của nhà phát triển (phát triển hệ thống hoàn hảo với ngôn ngữ hoàn hảo của bạn trong một môi trường biệt lập) lại không bao giờ là điều tổ chức mong muốn: đồng nhất hóa và tiêu chuẩn hóa. Điều này có nghĩa là một bộ ngôn ngữ, thư viện và công cụ tương đối chuẩn hóa để các nhà phát triển khác có thể tiếp tục hỗ trợ dịch vụ vi mô Haskell của bạn trong tương lai khi bạn chuyển sang những đồng cỏ xanh hơn. Hướng dẫn về Java Microservices.  Phần 1: Kiến trúc và kiến ​​trúc cơ bản về microservice - 8Lịch sử cho thấy sự tiêu chuẩn hóa thường đã ăn sâu vào tiềm thức. Ví dụ: các nhà phát triển tại các công ty lớn trong Fortune 500 đôi khi thậm chí không được phép sử dụng Spring vì nó "không nằm trong lộ trình công nghệ của công ty". Tuy nhiên, việc chuyển đổi hoàn toàn sang cách tiếp cận đa ngôn ngữ gần như giống nhau, mặt khác của cùng một đồng xu. Khuyến nghị: Nếu bạn định sử dụng lập trình đa ngôn ngữ, hãy thử ít đa dạng hơn trong cùng một hệ sinh thái ngôn ngữ lập trình. Vì vậy, tốt hơn là sử dụng Kotlin và Java cùng nhau (cả hai ngôn ngữ đều dựa trên JVM và tương thích 100% với nhau), thay vì sử dụng Java và Haskell. Trong phần tiếp theo, bạn sẽ tìm hiểu về cách triển khai và thử nghiệm các vi dịch vụ Java.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION