Chuyển đến nội dung chính

[Microservice] Các khái niệm chính trong microservice

1. Monolithic Architecture

Các ứng dụng doanh nghiệp ngày nay đang được thiết kế để đáp ứng được số lượng lớn nghiệp vụ kinh doanh. Do đó một ứng dụng phần mềm cần cung cấp hàng trăm chức năng và tất cả các những chức năng như vậy thường được gói gọn trong một ứng dụng nguyên khối duy nhất. ERP, CRM và các hệ thống phần mềm khác nhau là những ví dụ điển hình – chúng được xây dựng dưới dạng nguyên khối với hàng trăm chức năng. Việc triển khai, xử lý sự cố, mở rộng và nâng cấp các ứng dụng như vậy quả là một cơn ác mộng đối với bất kỳ doanh nghiệp nào.

Kiến trúc hướng dịch vụ (service-oriented architecture – SOA) được thiết kế để khắc phục các vấn đề phát sinh từ ứng dụng một khối (monolithic) bằng cách đưa ra khái niệm service. Do đó, với SOA, một ứng dụng sẽ được thiết kế dưới dạng kết hợp các service khác nhau. Khái niệm SOA không có nghĩa là biến từng service thành một khối riêng, nhưng hầu hết các ứng dụng triển khai theo SOA đều có hướng triển khai từng service dưới dạng một khối có cùng thời gian runtime. Vì vậy, tương tự như các ứng dụng một khối, các service này theo thời gian cũng tích lũy nhiều nghiệp vụ và chức năng khác nhau. Sự tăng trưởng này sẽ sớm biến những service đó thành những khối u nguyên khối, không khác gì những ứng dụng một khối thông thường.


Hình 1 cho thấy một ứng dụng phần mềm bán lẻ bao gồm nhiều dịch vụ. Tất cả các service này được deploy trong cùng một ứng dụng runtime. Do đó, nó cho thấy một số đặc điểm của một ứng dụng nguyên khối: nó phức tạp, được thiết kế, phát triển và deploy trong cùng một đơn vị duy nhất; nó quá khó để áp dụng các phương pháp phát triển linh hoạt và phân phối nhanh; cập nhật một phần ứng dụng sẽ bắt buộc phải triển khai lại toàn bộ ứng dụng.

Ngoài ra còn một số vấn đề đối với kiến trúc một khối: Nó sẽ khó được scale nếu có xung đột về yêu cầu resource (ví dụ như một service cần nhiều CPU hơn trong khi service khác lại cần nhiều bộ nhớ hơn). Một service không ổn định có thể làm cả ứng dụng bị chết, và thông thường, nó khó có thể đổi mới và áp dụng các công nghệ hay framework mới.

Những đặc điểm này là khởi đầu dẫn đến kiến trúc microservice ra đời. Hãy cùng xem xét cách thức hoạt động của nó.

2. Kiến trúc microservice

Nền tẳng của kiến trúc microservice (MSA) là về việc phát triển một ứng dụng bằng các service nhỏ và độc lập chạy trong tiến trình riêng của chúng. Các service này được phát triển và deploy một cách độc lập.

Hầu hết các định nghĩa về MSA giải thích nó như là một khái niệm kiến trúc tập trung vào việc tách các service sẵn có trong kiến trúc một khối thành một tập hợp các service độc lập. Tuy nhiên thì microservice không chỉ là làm những việc phân chia như thế.

Hãy xem xét điều đó bằng cách nhìn các chức năng trong kiến trúc một khối bằng cách xác định khả năng nghiệp vụ cần có từ ứng dụng – đó là trả lời câu hỏi ứng dụng cần làm gì, có ích hay không? Sau đó những khả năng đó có thể được thực hiện các service độc lập hoàn toàn, đủ tốt và khép kín. Chúng có thể được triển khai trên các stack công nghệ khác nhau, nhưng mỗi service sẽ giải quyết một phạm vi kinh doanh rất cụ thể và hạn chế.

Bằng cách này, kiến trúc hệ thống bán lẻ như hình 1 có thể được mô tả lại như sau:


Bây giờ hãy xem xét các nguyên tắc kiến trúc quan trọng của microservice và quan trọng hơn là tập trung vào cách chúng có thể được sử dụng trong thực tế.

3. Thiết kế Microservice: Size, Scope và Capabilities.

Bạn có thể làm một trong hai điều khi nói tới microservice: Một là bạn xây dựng ứng dụng của mình từ đầu và hai là bạn chuyển đổi ứng dụng/service của mình hiện có thành microservice. Dù bằng cách nào, điều quan trọng là bạn phải quyết định đúng kích thước, phạm vi và khả năng của các microservice. Đây có lẽ là điều khó nhất mà bạn cần gặp phải khi thực hiện MSA trong thực tế.

Dưới đây là một vài mối quan tâm chính và quan niệm sai lầm về chúng:

Số dòng code (line of code) / kích thước team (team size) là số liệu tệ hại: Có một số tranh luận về việc quyết định kích thước của microservice dựa trên số dòng code của việc triển khai hoặc team size. Tuy vậy, đây được coi là những số liệu rất không thực tế và tệ hại.
Micro là một thuật ngữ bị hiểu sai: Nhiều lập trình viên có xu hướng nghĩ rằng họ nên cố làm cho service nhỏ nhất có thể. Đây là một hiểu nhầm, không phải càng nhỏ thì càng tốt.
Trong bối cảnh web service, các service thường được triển khai ở các mức độ chi tiết khác nhau – từ một vài chức năng đến vài chục chức năng. Việc có các webservice và đặt chúng dưới dạng microservice sẽ không đem lại cho bất kỳ lợi ích nào của MSA.
Vậy làm thế nào chúng ta có thể thiết kế service đúng cách trong MSA:

3.1. Hướng dẫn thiết kế microservice

Single Responsibility Priciple (SRP): Mỗi service cần có phạm vi và bị giới hạn về nghiệp vụ cụ thể, không phụ thuộc lẫn nhau. Điều đó giúp cho chúng ta đáp ứng sự phát triển linh hoạt và nhanh chóng trong cung cấp service.
Trong giai đoạn thiết kế các microservice, chúng ta nên tìm ranh giới của chúng và tùy chỉnh chúng phù hợp với các nghiệp vụ (hay còn biết đến là giới hạn context trong DomainDriven-Design).
Chắc chắn việc thiết kế microservice đảm bảo sự phát triển và triển khai nhanh chóng, độc lập của service. Bạn nên tập trung vào phạm vi của microservice, không phải là cố làm cho nó nhỏ hơn.
Thường thì bạn nên bắt đầu với các ranh giới service rộng, sau đó tái cấu trúc cho các ranh giới nhỏ hơn (dựa trên nghiệp vụ) theo thời gian. Trong ví dụ về hệ thống bán lẻ ở trên, bạn có thể thấy nó đã phân chia các chức năng thành 4 microservice khác nhau là inventory, accounting, shipping và store. Chúng đang giải quyết một nghiệp vụ cụ thể và mỗi service đều không phụ thuộc lẫn nhau, đảm bảo phát triển và deploy nhanh chóng.

4. Message trong microservice

Trong kiến trúc một khối, các chức năng nghiệp vụ của các component / bộ xử lý khác nhau sẽ gọi nhau bằng cách sử dụng các lệnh hàm gọi hoặc các method gọi hàm của ngôn ngữ lập trình. Trong SOA, điều này đã được chuyển sang cách request/response các message service có tính lỏng lẻo hơn, chủ yếu dựa trên SOAP với nhiều giao thức khác nhau như HTTP, JMS.

4.1. Tin nhắn đồng bộ (Synchronous Messaging) – REST, Thrift

Đối với tin nhắn đồng bộ (client yêu cầu phản hồi kịp thời từ các service và thời gian đợi để nhận được nó) trong MSA, REST là lựa chọn dễ nhất vì nó cung cấp một kiểu message đơn giản với các request, respose HTTP. Do đó hầu hết các microservice đều sử dụng HTTP bên cạnh các tài nguyên khác (mỗi chức năng đại diện cho một tài nguyên và các hoạt động sẽ được thực hiện trên các tài nguyên đó).


Thift được sử dụng như một thay thế cho REST/HTTP trong đồng bộ tin nhắn.

4.2. Tin nhắn không đồng bộ (Asynchronous) – AMQP, STOMP, MQTT

Đối với một số hoàn cảnh chúng ta bắt buộc phải sử dụng các tin nhắn không đồng bộ ( client không muốn phản hồi ngay lập tức hoặc muốn hoàn toàn không phản hồi). Trong những tình huống này, các giao thức tin nhắn không đồng bộ như AMQP, STOPM hay MQTT được sử dụng phổ biến.

4.3. Message Format – JSON, XML, Thrift, ProtoBuf, Avro

Một yếu tố quan trọng khác là định dạng của message. Các ứng dụng một khối truyền thống sử dụng các định dạng nhị phân phức tạp, còn SOA/Web service lại sử dụng các message dựa trên các định dạng message phức tạp (SOAP) hay schema (xsd). Hầu hết các ứng dụng dựa trên microservice sử dụng các định dạng message dựa trên văn bản đơn giản như JSON hay XML trên HTTP REST API. Trong trường hợp chúng ta cần các định dạng message nhị phân (do message văn bản có thể trở nên dài dòng), microservice có thể tận dụng định dạng message nhị phân như binary Thrift, ProtoBuf hay Avro.

4.4. Service Contracts – Khai báo Service Interface – Swagger, RAML, Thrift IDL

Khi bạn có một nghiệp vụ có thể làm thành một service, bạn sẽ cần làm những bản service contract (đại loại một văn bản mà bạn muốn những client khác gọi đến phải tuân theo những nguyên tắc trong đó để trích xuất được dữ liệu). Trong ứng dụng một khối truyền thống, chúng ta hầu như không cần làm bởi vì các service bên trong có thể gọi nhau qua framework hoặc ngôn ngữ nền tảng. Trong SOA/Web service, WSDL thường được dùng để làm service contract, nhưng WSDL không phải là giải pháp trong một hệ thống microservice dùng REST để giao tiếp giữa các service.

Bởi vì chúng ta xây dựng microservice dựa trên kiến trúc REST nên chúng ta có thể sử dụng các kỹ thuật định nghĩa API trong REST để tạo một service contract. Do đó, chúng ta có thể sử dụng Swagger và RAML để định nghĩa service contract.

Đối với các hệ thống không dựa trên HTTP/REST, chẳng hạn như Thrift, chúng ta có thể dùng Interface Definition Languages (IDL) như Thrift IDL.

5. Quản lý dữ liệu phân tán

Trong kiến trúc một khối, ứng dụng lưu trữ dữ liệu trong các cơ sở dữ liệu đơn và tập trung để thực hiện các chức năng / khả năng khác nhau của ứng dụng.


Trong MSA, các chức năng được phân tán trên nhiều microservice và nếu chúng ta sử dụng cùng một cơ sở dữ liệu tập trung thì nó rất khó đảm bảo tính lỏng lẻo giữa các service bởi nếu nếu database schema có sự thay đổi ở một service nào đó có thể sẽ làm chết một vài service khác. Do đó, mỗi service cần phải có cơ sở dữ liệu riêng.

Dưới đây là các khía cạnh chính của việc thực hiện quản lý dữ liệu phân tán trong MSA:

Mỗi microservice có thể có một cơ sở dữ liệu riêng để duy trì dữ liệu cần thực hiện chức năng nghiệp vụ của nó.
Một microservice cụ thể chỉ có thể truy cập vào cơ sở dữ liệu riêng của nó, chứ không phải cơ sở dữ liệu của các service khác.
Trong một số tình huống, bạn có thể phải cập nhật một số cơ sở dữ liệu cho một giao dịch. Trong các trường hợp như vậy, cơ sở dữ liệu của các service khác chỉ được cập nhật thông qua API của nó (không được phép truy cập trực tiếp vào cơ sở dữ liệu).
Việc quản lý dữ liệu phân tán sẽ cung cấp cho bạn các service tách rời hoàn toàn và tự do lựa chọn các kỹ thuật quản lý dữ liệu khác nhau (SQL hoặc NoSQL,… các hệ thống quản trị cơ sở dữ liệu khác nhau cho mỗi service). Tuy nhiên, đối với các trường hợp sử dụng transaction phức tạp liên quan đến nhiều service, các transaction phải được thực hiện bằng cách sử dụng API được cung cấp từ mỗi service và logic tại client hoặc các tầng trung gian (Gateway).

6. Quản trị phân tán

MSA rất thích hợp để quản trị phân tán. Quản trị trong IT được hiểu là các quy trình đảm bảo việc sử dụng công nghệ hiệu quả và cho phép một tổ chức đạt được mục tiêu của mình. Trong SOA, quản trị SOA hướng dẫn phát triển các service có thể sử dụng lại, thiết lập cách thức các service sẽ được thiết kế, phát triển và cách các service đó thay đổi theo thời gian. Nó thiết lập các thỏa thuận giữa bên cung cấp service và người sử dụng service, nói cho người sử dụng biết những gì họ mong muốn và cho bên cung cấp biết những gì họ bắt buộc phải cung cấp. Quản trị SOA có hai loại quản trị thường được dùng phổ biến:

Quản trị Design-time: xác định và kiểm soát cách tạo, thiết kế và phát triển service theo policy.
Quản trị Run-time: xác định khả năng thực thi các policy trong suốt thời gian thi hành.
Vậy quản trị trong microservice có ý nghĩa là gì? Trong MSA, microservice được xây dựng dưới các service độc lập và tách rời hoàn toàn với các nền tảng công nghệ khác nhau. Do đó không cần xác định một tiêu chuẩn chung cho thiết kế và phát triển service. Chúng ta có thể tóm tắt quản trị phân tán của microservice như sau:

Các service có thể tự đưa ra những nguyên tắc về thiết kế và cách thực hiện của riêng nó.
MSA khuyến khích việc chia sẻ các service chung hoặc có thể tái sử dụng.
Các khía cạnh quản trị run-time như SALs, điều tiết, giám sát, các yêu cầu chung về bảo mật và service discovery sẽ không được triển khi trên mỗi service. Thay vào đó, chúng nên được ở trong các component chuyên dụng (thường ở tầng API-gateway).

7. Service Registry và Service Discovery

Trong MSA, số lượng các service mà bạn cần để làm việc sẽ là khá nhiều. Chúng thay đổi vị trí linh hoạt do tính chất cần phát triển nhanh của microservice. Do đó, bạn cần tìm vị trí của microservice trong suốt thời gian runtime. Giải pháp cho vấn đề này là sử dụng Service Registry.

Service Registry
Service registry là nơi để chứa các metadata của các microservice instances (bao gồm vị trí location, host port,…). Các microservice instance được đăng ký với service registry khi khởi động và sẽ hủy đăng ký khi bị shut down. Các thành phần khác cần tìm thông tin của một microservice nào đó thì sẽ tìm thông qua service registry.

Service Discovery
Để tìm các microservice đang hoạt động và vị trí location của nó, chúng ta cần đến cơ chế service discovery. Có 2 loại cơ chế service discovery là client-side discovery và server-side discovery. Chúng ta sẽ xem xét kỹ hơn về các cơ chế này:

Client-side discovery: Theo cách tiếp cận này, client hoặc API-gateway sẽ có được vị trí của một service instance bằng cách truy vấn một service registry.



Server-side discovery: Với phương pháp này, client/API gateway sẽ gửi một request đến một component (ví dụ như một load balancer) chạy trên một location đã biết. Component đó sẽ gọi đến service registry và xác định vị trí location mà request cần đến.

Các microservice có thể tận dụng các giải pháp deployment như Kubernetes cho service-side discovery.

8. Deployment

Khi nói đến MSA, việc deploy các service đóng một vai trò quan trọng và có các yêu cầu chính như sau:

Khả năng deploy/undeploy độc lập đối với các service khác.
Phải có khả năng scale cho từng service.
Deploy nhanh chóng.
Lỗi trong một service sẽ không được làm ảnh hưởng đến bất kỳ service nào khác.
Docker (một công cụ mã nguồn mở cho phép các lập trình viên và quản trị viên hệ thống deploy các container trong mỗi trường Linux) cung cấp một cấp tuyệt vời để deploy các service theo các yêu cầu trên. Các ý chính bao gồm:

Package của service dưới dạng một Docker image.
Deploy mỗi service instance bằng một container.
Scale được thực hiện dựa trên việc thay đổi số lượng container instance.
Build, deploy và khởi động một service sẽ nhanh hơn nhiều khi sử dụng Docker container (so với các máy ảo VM thông thường).
Kubernetes là một công cụ có khả năng mở rộng các tính năng của Docker bằng cách cho phép quản lý một cụm (cluster) Linux container dưới dạng một hệ thống duy nhất, quản lý và chạy các Docker container trên nhiều máy chủ, cung cấp các vị trí cùng cấp (co-location) của các container, service discovery và các kiểm soát khác. Như bạn có thể thấy, hầu hết các tính năng này đều rất cần thiết trong hệ thống microservice. Do đó, sử dụng Kubernetes (dựa trên Docker) để triển khai hệ thống microservice đã trở thành một giải pháp mạnh mẽ, đặc biệt là đối với các hệ thống có quy mô lớn.


9. Bảo mật

Bảo mật là một yêu cầu quan trọng và phổ biến khi bạn sử dụng microservice trong thực tế. Trước khi tìm hiểu bảo mật trong microservice, chúng ta sẽ xem xét qua cách mà chúng ta thường triển khai bảo mật ở một ứng dụng một khối.

Trong một ứng dụng nguyên khối điển hình, bảo mật là tìm ra “ai là người gọi”, “người gọi có thể làm gì” và “chúng ta truyền thông tin đó như thế nào”.
Điều này thường được thực hiện tại một component bảo mật chung, nằm ở đầu chuỗi xử lý yêu cầu và component đó chứa thông tin cần thiết với việc sử dụng vùng lưu trữ người dùng bên dưới.
Vậy thì chúng ta có thể trực tiếp sử dụng lại các nguyên tắc này ở MSA không? Câu trả lời là có, nhưng nó đòi hỏi một component bảo mật được triển khai ở từng service có thể giao tiếp với vùng lưu trữ thông tin user và truy xuất thông tin cần thiết. Đó là một cách giải quyết rất tệ trong bảo mật microservice.

Thay vào đó, chúng ta có thể tận dụng các tiêu chuẩn bảo mật API đang được dùng phổ biến như OAuth 2.0, OpenID Connect để tìm giải pháp tốt hơn cho vấn đề bảo mật. Trước khi đi sâu vào vấn đề đó, trước tiên hãy tóm tắt mục đích của từng tiêu chuẩn và cách chúng ta có thể sử dụng chúng.

OAuth 2.0 Là một giao thức ủy quyền truy cập. Client xác thực với một server ủy quyền và nhận được mã token, được gọi là ‘Access token’. Một access token sẽ không có thông tin nào về user/client. Nó chỉ có một tham chiếu đến thông tin user chỉ có thể truy xuất bởi server ủy quyền. Do đó nó còn được gọi là by-reference token và nó đủ an toàn ngay khi trong mỗi trường mạng internet công khai.
OpenID Connect hoạt động tương tự như OAuth, nhưng ngoài access token, server ủy quyền sẽ phát hành mã token ID có chứa thông tin về người dùng. Điều này thường được triển khai bằng JWT (JSON Web Token) và nó được đăng ký bởi server ủy quyền. Điều này đảm bảo sự tin tưởng giữa server ủy quyền và client. Do đó, JWT còn được gọi là by-value token vì nó chứa thông tin của người dùng và rõ ràng không an toàn khi sử dụng bên ngoài mạng nội bộ.
Bây giờ, hãy xem cách chúng ta có thể sử dụng các tiêu chuẩn này để bảo mật các service trong ví dụ cửa hàng bán lẻ lúc đầu:


Như đã mô tả trong hình trên, các bước chính cần có liên quan đến việc triển khai bảo mật trong hệ thống microservice là:

Để lại việc xác thực cho OAuth 2.0 và OpenID Connect (server ủy quyền), nhờ vậy microservice sẽ cung cấp quyền truy cập thành công khi ai đó có quyền sử dụng dữ liệu.
Sử dụng API-gateway trong đó có một entry-point duy nhất cho tất cả các yêu cầu của máy khách.
Client kết nối với server ủy quyền và nhận access token (by reference token). Sau đó sẽ gửi các access token kèm theo trong mỗi lần request đến API-gateway.
Token sẽ được giải mã ở gateway – API-gateway trích xuất access token và gửi nó đến server ủy quyền để truy xuất JWT. (by value-token).
Cổng gateway sẽ dùng mã JWT kèm theo mỗi lần request đến tầng microservice.
JWT chứa các thông tin cần thiết để lưu trữ các phiên người dùng… Nếu mỗi service đều có thể hiểu một JSON token thì bạn có thể phân phối cơ chế nhận dạng của mình mà cho phép vận chuyển danh tính của người dùng trên toàn hệ thống của bạn.
Ở mỗi lớp microservice, chúng ta có thể có một component nhỏ để xử lý JWT.
10. Inter-Service/Process Communication
Trong MSA, các ứng dụng được xây dựng như một tập hợp các service độc lập. Do đó để biết khi nào cần đến nghiệp vụ nào thì chúng ta cần phải có các cấu trúc giao tiếp giữa các service hay quy trình khác nhau. Từ khi microservice sử dụng các chuẩn giao thức (như HTTP) và các định dạng message (như JSON,…) thì yêu cầu tích hợp với một giao thức khác là yêu cầu tối thiểu khi nhắc đến việc giao tiếp giữa các microservice. Một cách tiếp cận khác trong giao tiếp microservice là sử dụng bus message hoặc cổng gateway với khả năng định tuyến tối thiểu và chỉ hoạt động như một ‘dumb pipe’ không triển khai bất kì nghiệp vụ nào trên gateway. Dựa trên những phương pháp này, chúng ta có một số kiểu giao tiếp như sau:


Nguồn : 

http://cdn.wso2.com/wso2/sites/all/images/pdf/microservices-in-practice-key-architectural-concepts-of-an-msa.pdf

Nhận xét

Bài đăng phổ biến từ blog này

Đa ngôn ngữ trong Angular  Trong phần này mình sẽ thêm chức năng đa ngôn ngữ cho project angular. Chúng ta sử dụng thư viện @ngx-translate cho bài viết, bạn có thể tham khảo ở  @ngx-translate   Trước tiên chúng ta cần tạo project và import thư viện: ng new angular-translate cd  angular-translate npm install @ngx-translate/core --save npm install @ngx-translate/http-loader --save Sau đó chúng ta import thư viện  TranslateModule  vào app.module.ts: -Tiếp đó chúng ta tao 2 file en.json và vn.json vào thư mục /assets/i18n/en.json -Tiếp đó chúng ta thiết lập sự kiện click chuyển ngôn ngữ trong file app.component.ts: - Để hiển thị trên giao diện, chúng ta sử dụng service TranslateService đã được khai báo trong contructor() như một pipe: Kết quả : Bằng việc truyền value vào event switchLanguage() chúng ta có thể thay đổi ngôn ngữ của trang Như vậy chúng ta đã hoàn thành xong việ...
  Giới thiệu về Spring Integration Giới thiệu      Spring Integration là một framework cho phép việc cấu hình động các tầng của hệ thống.  Nó giúp cho việc cấu hình linh động, dàn xếp các module trở nên độc lập, dễ dàng thay đổi phù hợp mục đích nâng cấp (xem hình 1 ) Hình - 1    Spring Integration cho phép nhắn tin giữa các module dựa trên Spring-based applications và nó hỗ trợ tích hợp với các hệ thông bên ngoài thông qua bộ khai báo. Mục đích chính của Spring Integration là cung cấp một mô hình đơn giản để xây dựng các giải pháp tích hợp doanh nghiệp trong khi vẫn duy trì cac mối liên kết cần thiết để có thể kiểm tra, bảo trì.    Nó đưa khái niệm về POJO lên thêm một bước nữa, nơi mà POJO được kết nối với nhau bằng cách sử dụng một mô hình nhắn tin và các thành phần riêng lẻ không thể nhận biết được các thành phần khác trong ứng dụng. Một ứng dụng được xây dựng bằng cách lắp ráp các thành phần, có thể tái sử dụng, tạ...
Get data from an API service in Angular  1. Promises và Observables  Promises:     Là một đối tượng được lập trình để thao tác bất đồng bộ trong Javascript. Nó đại diện cho một giá trị mà chúng ta muốn thao tác nhưng có thể hiện tại chưa có sẵn nhưng trong tương lại nó có giá trị trong promise đó. Một số đặc điểm của promise là: - Chỉ trả về một giá trị duy nhất, đó có thể là object, array, number.... - Không thể cancel được request - Được khởi tạo ngay mặc dù chưa được đăng ký, miễn là bạn khai báo một promise thì nó sẽ chạy contructor. Promise có ba trạng thái : Pending: khi khởi tạo đối tượng promise, gọi một request đến server. Fulfilled: thao tác gọi đến server thành công sẽ chuyển từ pending sang fulfilled qua phương thức then() (có thể xử lý kết quả thành công trong phương thức then() ). Rejected: khi request đến server bị lỗi, có thể dùng .catch() để bắt lỗi. Observables:     Tương tự như promise nhưng có mộ...