Tag: Software Architecture

  • Mô đun hoá theo tầng hay mô đun hoá theo tính năng?

    Mô đun hoá theo tầng hay mô đun hoá theo tính năng?

    Mô đun hoá là quá trình tách một hệ thống phần mềm thành nhiều mô đun. Ngoài việc giảm độ phức tạp, nó làm tăng tính dễ hiểu, khả năng bảo trì và khả năng sử dụng lại của hệ thống. Trong bài viết này sẽ đề cập đến hai phương pháp mô đun hoá (theo tầng và theo tính năng). Chúng ta nên chọn phương pháp nào và tại sao?

    Trước khi đến với nội dung chính chúng ta cùng xem một số nội dung liên quan:

    1. KIẾN TRÚC PHÂN TẦNG (LAYERED ARCHITECTURE) (PHẦN 1)
    2. KIẾN TRÚC PHÂN TẦNG (LAYERED ARCHITECTURE) (PHẦN 2)
    3. ÁP DỤNG KIẾN TRÚC PHÂN TẦNG TRONG ỨNG DỤNG SPRING BOOT

    Mô đun hoá theo tầng

    Khi áp dụng kiến trúc phân tầng vào các dự án kiểu này, các class được đặt trong các package dựa theo tầng trong kiến trúc phân tầng mà chúng thuộc về. Phương pháp này làm giảm tính gắn kết (low cohesion) giữa các class bên trong các package bởi vì trong cùng một package có chứa các class không liên quan chặt chẽ với nhau. Dưới dây là một ví dụ áp dụng phương pháp mô đun hoá theo tầng.

    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── app
    │   │   │       └── demo
    │   │   │           ├── DemoApplication.java
    │   │   │           ├── controller
    │   │   │           │   ├── OrderController.java
    │   │   │           │   └── ProductController.java
    │   │   │           ├── entity
    │   │   │           │   ├── Order.java
    │   │   │           │   └── Product.java
    │   │   │           ├── repository
    │   │   │           │   ├── OrderRepository.java
    │   │   │           │   └── ProductRepository.java
    │   │   │           ├── request
    │   │   │           │   ├── OrderCreateRequest.java
    │   │   │           │   └── ProductCreateRequest.java
    │   │   │           ├── response
    │   │   │           │   ├── OrderCreateResponse.java
    │   │   │           │   └── ProductCreateResponse.java
    │   │   │           └── service
    │   │   │               ├── OrderService.java
    │   │   │               ├── OrderServiceImpl.java
    │   │   │               ├── ProductService.java
    │   │   │               └── ProductServiceImpl.java
    

    Ngoài ra khi kiểm tra cấu trúc của các dự án như trên chúng ta thấy rằng giữa các package có liên kết chặt chẽ với nhau (high coupling). Bởi vì các class ở tầng Repository được sử dụng trong các class ở tầng Service và các class ở tầng Service được sử dụng trong các class ở tầng Controller. Hơn nữa, mỗi khi có yêu cầu thay đổi chúng ta cần phải thay đổi ở nhiều package khác nhau.

    Để có thể giúp một việc nào đó, chúng ta cần phải biết mọi thứ.

    CohesionCoupling nghĩa là gì?

    • Cohesion: Cohesion đề cập đến mức độ quan hệ logic giữa các class trong cùng package với nhau. High-cohesion giữa các class đảm bảo tính độc lập của package. Low-cohesion không chỉ giảm tính độc lập mà còn giảm đáng kể khả năng sử dụng lại và tính dễ hiểu.
    • Coupling: Coupling đề cập đến mức độ phụ thuộc lẫn nhau giữa các package/class. Low-coupling làm tăng đáng kể khả năng bảo trì. Bởi vì những thay đổi được thực hiện bên trong class do yêu cầu thay đổi không ảnh hưởng đến các class khác, không có tác dụng phụ và việc bảo trì dễ dàng hơn.

    High-cohesion bên trong các packagelow-coupling giữa các package là thiết yếu đối với một hệ thống được thiết kế tốt. Một thiết kế tốt làm tăng đáng kể tính bền vững của hệ thống. Vậy thì làm thế để đạt được điều đó?

    Mô đun hoá theo tính năng

    Dưới đây là một ví dụ áp dụng phương pháp mô đun hoá theo tính năng.

    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── app
    │   │   │       └── demo
    │   │   │           ├── DemoApplication.java
    │   │   │           ├── domain
    │   │   │           │   ├── order
    │   │   │           │   │   └── create
    │   │   │           │   │       ├── OrderCreateController.java
    │   │   │           │   │       ├── OrderCreateRequest.java
    │   │   │           │   │       ├── OrderCreateResponse.java
    │   │   │           │   │       ├── OrderCreateService.java
    │   │   │           │   │       └── OrderCreateServiceImpl.java
    │   │   │           │   └── product
    │   │   │           │       └── create
    │   │   │           │           ├── ProductCreateController.java
    │   │   │           │           ├── ProductCreateRequest.java
    │   │   │           │           ├── ProductCreateResponse.java
    │   │   │           │           ├── ProductCreateService.java
    │   │   │           │           └── ProductCreateServiceImpl.java
    │   │   │           ├── entity
    │   │   │           │   ├── Order.java
    │   │   │           │   └── Product.java
    │   │   │           └── repository
    │   │   │               ├── OrderRepository.java
    │   │   │               └── ProductRepository.java
    

    Trong cấu trúc dự án kiểu này, các package chứa tất cả các class được yêu cầu bởi một tính năng. Tính độc lập của package dượcd đảm bảo bằng cách đặt các class có liên quan chặt chẽ trong cùng một package.

    Việc sử dụng một class bởi một class trong gói khác được loại bỏ ở cấu trúc này. Ngoài ra, các class trong cùng một package có liên quan chặt chẽ với nhau. Vì vậy high-cohesion trong cùng một packagelow-coupling giữa các package được đảm bảo bởi cấu trúc này.

    Hơn nữa, cấu trúc này làm tăng tính mô đun hoá. Giả sử rằng chúng ta có thêm 10 domain (ngoài ProductOrder). Với phương pháp mô đun hoá theo tầng, các class sẽ được đặt trong các package controller, service, repository. Vì vậy toàn bộ ứng dụng sẽ bao gồm 3 package (ngoại trừ các class tiện ích), các package sẽ có số lượng lớn class. Tuy nhiên, trong phương pháp mô đun hoá theo tính năng, cùng ứng dụng đó sẽ bảo gồm 12 package tương ứng với 12 domain, tính mô đun hoá đã được tăng lên.

    Trong ví dụ trên chúng ta thấy có 2 ngoại lệ, repositoryentity package không được cấu trúc theo tính năng như bình thường. Với các entity và các repository được sử dụng ở nhiều service khác nhau, do chúng không là bắt buộc ở một tính năng cụ thể nào nên chúng ta cấu trúc chúng theo phương pháp mô đun theo tầng như bình thường. Với những entityrepository chỉ được sử dụng ở một tính năng cụ thể nào đó, chúng ta vẫn cấu trúc chúng theo phương pháp mô đun hoá theo tính năng như bình thường.

    Nếu một tính năng có thể được xoá bởi chỉ một hành động, ứng dụng đó có tính mô đun hoá cao nhất.

    Lợi ích của việc mô đun hoá theo tính năng

    • Mô đun hoá theo tính năng tạo ra các packagehigh-cohesion, low-coupling và tính mô đun hoá cao.
    • Mô đun hoá theo tính năng cho phép các class được khai báo với thuộc tính truy cập là private thay vì public, đo đó tăng tính đóng gói. Mặt khác mô đun hoá theo tầng buộc chúng ta phải đặt gần như toàn bộ các classpublic.
    • Mô đun hoá theo tính năng giúp giảm việc phải điều hướng giữa các package bởi vì các class cần thiết cho một tính năng được đặt trong cùng một package.
    • Mô đun hoá theo tính năng giống như kiến trúc microservice. Mỗi package được giới hạn bởi các class liên quan với một tính năng cụ thể. Mặt khác, mô đun hoá theo tầng giống như kiến trúc nguyên khối. Khi một ứng dụng tăng kích thước, số class trong mỗi package sẽ tăng lên không giới hạn.

    Tổng kết

    Martin Fowler gợi ý bắt đầu một dự án mới với kiến trúc microservice có thể không phải là một ý kiến hay. Nếu ứng dụng của chúng ta đạt mức tăng trưởng lớn và giới hạn của nó là chắc chắn, thì bạn nên chuyển sang kiến trúc microservice.

    Hãy tưởng tượng tình huống trên, chúng ta đã quyết định tách các microservice từ ứng dụng nguyên khối. Giả sử rằng microservice sử dụng phương pháp mô đu hoá theo tính năng, vậy cấu trúc nào sẽ dễ dàng chuyển sang kiến trúc microservice hơn?

    Câu trả lời cho câu hỏi này và các ưu điểm khác giúp chúng ta biết nên sử dụng phương pháp mô đun hoá nào.

  • Áp dụng kiến trúc phân tầng trong ứng dụng Spring Boot

    Áp dụng kiến trúc phân tầng trong ứng dụng Spring Boot

    Trong bài viết này chúng ta sẽ cùng tìm hiểu kiến trúc phân tầng được ứng dụng như thế nào trong ứng dụng Spring Boot.

    Chúng ta nên sử dụng bao nhiêu tầng?

    Trong kiến trúc phân tầng chúng ta không bị hạn chế về số tầng. Tuy nhiên, các dự án trong thực tế triển khai thường sử dụng 4 tầng. Các ứng dụng Spring Boot cũng có thể triển khai kiến trúc phân tầng với 4 tầng như sau:

    • Tầng Controller là triển khai của tầng Presentation.
    • Tầng Service là triển khai của tầng Business.
    • Tầng Repository là triển khai của tầng Persistence.
    • Tầng Database không được phản ánh trong mã nguồn của ứng dụng.

    Do một ứng dụng có hoặc không cần sử dụng tới database. Khi đó có thể tầng Repository cũng không tồn tại. Trong thực tế chúng ta thường thấy các Entity được định nghĩa trong mã nguồn. Về mặt lí thuyết thì các Entity thuộc về tầng Persistence. Tuy nhiên các Entity chính là phản ánh của các bảng trong database. Do đó chúng ta có thể xem các Entity như là thể hiện của tầng Database.

    Kiến trúc phân tầng trong ứng dụng Spring Boot

    Dưới đây là một ví dụ triển khai của kiến trúc phân tầng với ứng dụng Spring Boot:

    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── app
    │   │   │       └── demo
    │   │   │           ├── DemoApplication.java
    │   │   │           ├── controller
    │   │   │           │   └── ProductController.java
    │   │   │           ├── entity
    │   │   │           │   └── Product.java
    │   │   │           ├── repository
    │   │   │           │   └── ProductRepository.java
    │   │   │           ├── request
    │   │   │           │   └── ProductCreateRequest.java
    │   │   │           ├── response
    │   │   │           │   └── ProductCreateResponse.java
    │   │   │           └── service
    │   │   │               ├── ProductService.java
    │   │   │               └── ProductServiceImpl.java
    

    Mỗi một request từ client sẽ lần lượt đi qua các tầng Controller, Service, Repository và kết thúc ở tầng Database. Trong ví dụ trên, bảng Product trong database sẽ được ánh xạ tương ứng với Product entity. Các thao tác tương tác với bảng Product sẽ được triển khai ở ProductRepository. Logic nghiệp vụ liên quan tới Product sẽ được cài đặt trong ProductService. ProductController sẽ là nơi tiếp nhận yêu cầu từ phía client.

    Các yêu cầu từ phía client sẽ được tiếp nhận ở ProductController. Sau đó các thông tin nhận được sẽ được truyền tới ProductService. Tại đây các logic nghiệp vụ liên qua sẽ được xử lí trước khi được truyền tới ProductRepository. Cuối cùng dữ liệu sẽ được lưu trữ trong bảng Product của có sở dữ liệu.

    Tầng Controller

    /**
     * ProductController.
     *
     * @author Hieu Nguyen
     */
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/products")
    public class ProductController {
      private final ProductService productService;
    
      @PostMapping
      public ResponseEntity<ProductCreateResponse> create(@RequestBody ProductCreateRequest request) {
        return ResponseEntity.ok().body(ProductCreateResponse.of(productService.create(request)));
      }
    }
    

    ProductController nhận dữ liệu từ client trong request body thông qua ProductCreateRequest. Sau đó nó truyền dữ liệu ProductCreateRequest xuống cho ProductService. Kết quả trả lại từ ProductService được chuyển thành ProductCreateResponse để trả lại client trong response body. ProductCreateRequestProductCreateResponse được cài đặt như sau:

    /**
     * ProductCreateRequest.
     *
     * @author Hieu Nguyen
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProductCreateRequest {
      private String name;
    
    }
    
    /**
     * ProductCreateResponse.
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class ProductCreateResponse {
      private Integer id;
    
      private String name;
    
      public static ProductCreateResponse of(Product product) {
        return ProductCreateResponse.builder().id(product.getId()).name(product.getName()).build();
      }
    }
    

    Tầng Service

    /**
     * ProductRepository.
     *
     * @author Hieu Nguyen
     */
    @Service
    @RequiredArgsConstructor
    public class ProductServiceImpl implements ProductService {
      private final ProductRepository productRepository;
    
      @Override
      @Transactional
      public Product create(ProductCreateRequest request) {
        return productRepository.save(Product.of(request));
      }
    }
    

    Tại tầng Service, ProductService nhận dữ liệu thông qua ProductCreateRequest được truyền xuống từ tầng Controller. Nó chuyển dữ liệu ProductCreateRequest vào Product entity, sau đó truyền dữ liệu Product entity xuống ProductRepository và cuối cùng dữ liệu được insert vào database.

    Tầng Repository

    Trong ví dụ này chúng ta sử dụng Spring Data JPAHibernate để cài đặt tầng Repository.

    /**
     * ProductRepository.
     *
     * @author Hieu Nguyen
     */
    @Repository
    public interface ProductRepository extends JpaRepository<Product, Integer> {}
    

    Chúng ta ánh xạ bảng Product vào Product entity như sau:

    /**
     * Product.
     *
     * @author Hieu Nguyen
     */
    @Data
    @Entity
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Product {
    
      @Id
      @GeneratedValue(generator = "Product")
      @TableGenerator(name = "Product", table = "hibernate_sequence")
      private Integer id;
    
      private String name;
    
      public static Product of(ProductCreateRequest request) {
        return Product.builder().name(request.getName()).build();
      }
    }
    

    Sử dụng tầng đóng hay mở

    Tất cả các tầng nên là đóng, nghĩa là với bất kì một tính năng nào chúng ta cần triển khai đủ 4 tầng: Controller, Service, Repository, Database. Tuy nhiên, trong qua trình sử dùng kiến trúc phân tầng sẽ có nhiều bạn đặt câu hỏi liệu có thực sự cần đến tầng Service không? Trên thực tế có nhiếu tính năng chúng ta sẽ không cần cài đặt mã nguồn ở tầng Service. Khi đó tẩng Service chỉ làm nhiệm vụ chuyển tiếp dữ liệu từ tầng Controller xuống tầng Repository. Tuy nhiên để đảm bảo không có những sai phạm không đáng có như việc cài đặt các logic nghiệp vụ ở tầng Controller sau đó gọi trực tiếp tới tầng Repository thì chúng ta nên triển khai tất cả các tầng đóng. Khi đó thì dù có hay không có logic nghiệp vụ ở tầng Service chúng ta vẫn nên triển khai tầng này.

    Tổng kết

    Trong bài viết này chúng ta đã cùng tìm hiểu cách triển khai kiến trúc phân tầng trong một ứng dụng Spring Boot. Việc triển khai thực sự không khó, tuy nhiên để đảm bảo việc triển khai được thống nhất thì thực sự rất khó. Bài viết này hi vọng rằng có thể đem lại cái nhìn thống nhất giữa tất các các thành viên trong một dự án. Khi đó việc áp dụng kiến trúc phân tầng sẽ có hiệu quả hơn.

  • Kiến trúc phân tầng (Layered Architecture) (Phần 2)

    Kiến trúc phân tầng (Layered Architecture) (Phần 2)

    Trong phần 1 chúng ta đã tìm hiều về Kiến trúc phân tầng và các khái niệm quan trọng nhất của nó. Trong phần 2 này chúng ta sẽ xem xét cách thức hoạt động của kiến trúc phân tầng và những điểm cần lưu ý khi sử dụng kiến trúc này.

    Ví dụ

    Để minh họa cách thức hoạt động của kiến trúc phân lớp, chúng ta cùng xem xét một yêu cầu từ người dùng doanh nghiệp muốn truy xuất thông tin khách hàng của một cá nhân cụ thể.

    Các mũi tên màu đen thể hiện luồng yêu cầu xuống cơ sở dữ liệu để lấy thông tin khách hàng. Các mũi tên màu đỏ thể hiện luồng phản hồi ngược trở lại màn hình để hiển thị dữ liệu. Trong ví dụ này, thông tin khách hàng bao gồm cả dữ liệu khách hàng và dữ liệu đơn hàng (đơn hàng do khách hàng đặt). Customer Screen có nhiệm vụ tiếp nhận yêu cầu và hiển thị thông tin khách hàng. Nó hoàn toàn không biết nơi dữ liệu được lư trữ, làm thế nào để lấy nó hoặc có bao nhiêu bảng cơ sở dữ liệu phải được truy vấn để lấy dữ liệu. Khi Customer Screen nhận được yêu cấu lấy thông tin khác hàng của một cá nhân cụ thể, nó sẽ chuyển tiếp yêu cầu đó tới mô đun Customer Delegate. Mô đun này có nhiệm vụ biết các mô đun ở tầng nghiệp vụ có thể xử lí yêu cầu đó cũng như làm thế nào để lấy các mô đun đó và dữ liệu nào mà nó cần (hợp đồng). Customer Object ở tầng nghiệp vụ có nhiệm vụ tổng hợp toàn bộ thông tin cần thiết bởi yêu cầu nghiệp vụ(trong trường hợp này là thông tin khách hàng). Mô đun này gọi ra Customer DAO (đối tượng truy cập dữ liệu) ở tầng lưu trữ để lấy dữ liệu khách hàng cùng với Order DAO để lấy thông tin đơn hàng. Các mô đun này lần lượt thực thi các câu lệnh SQL để lấy dữ liệu tương ứng và trả lại cho Customer Object ở tầng nghiệp vụ. Khi Customer Object nhận được dữ liệu, nó sẽ tổng hợp dữ liệu và trả lại các thông tin đó cho Customer Delegate, sau đó Customer Delegate trả lại các dữ liệu đó cho Customer Screen để hiển thị cho người dùng. Từ khía cạnh công nghệ, có hàng tá cách để cài đặt các mô đun này. Ví dụ với nên tàng Java, Customer Screen có thể là một màn hình JSF(Java Server Faces) cùng với Customer Delete là thành phần bean được quản lí. Customer Object ở tầng nghiệp vụ có thể là một Spring Bean cục bộ hoặc EJB3 bean từ xa. Các đối tượng truy cập cơ sở dữ liệu được minh hoạ trong ví dụ trước có thể được triển khai dưới dạng POJO (Plain Old Java Objects) đơn giản, MyBatis XML Mapper, hoặc ngay cả cá đối tượng đóng gọi lời gọi JDBC thuần hoặc các truy vấn Hibernate. Trên nền tảng Microsoft, Customer Screen có thể là một mô đun ASP(Active Server Pages) sử dụng framework .NET để truy cập các mô đun C# ở tầng nghiệp vụ với các mô đun truy cập dữ liệu khác hàng và đơn hàng được triển khai dưới dàng ADO(ActiveX Data Objects).

    Những điểm cần cân nhắc khi sử dụng kiến trúc phân tầng

    Kiến trúc phân tầng là một mẫu kiến trúc về cơ bản là vững chắc, hấu hết các ứng dụng có thể bắt đầu bằng kiến trúc này, đặc biệt khi chúng ta không chắc chắn kiến trúc nào là phù hợp nhất cho ứng dụng của chúng ta. Tuy nhiên về mặt kiến trúc thì có một vài điều cần cân nhắc trước khi chọn mẫu kiến trúc này.

    Điểm đầu tiên cần chú ý là các anti-pattern(phản mẫu – các mẫu thiết kế cần tránh sử dụng). Một trong số đó là kiến trúc hố sụt. Kiến trúc này mô tả tình huống luồng yêu cầu đi qua nhiều tầng của kiến trúc mà đơn giản xử lí chuyển tiếp với rất ít hoặc không có logic được thực hiện bên trong mỗi tầng. Ví dụ, tầng trình diễn phản hồi một yêu cầu từ người dùng muốn lấy dữ liệu khách hàng. Tầng trình diễn chuyển yêu cầu tới tầng nghiệp vụ, nó đơn giản chuyển tiếp yêu cầu tới tầng lưu trữ, sau đó tạo một lời gọi SQL đơn giản tới tầng cơ sở dữ liệu để lấy dữ liệu khách hàng. Dữ liệu sau đó được truyền theo đường ngược lại mà không có xử lí thêm hay logic tổng hợp, tính toán hoặc biến đổi dữ liệu.

    Mỗi kiến trúc phân tầng sẽ có ít nhất một số tình huống rơi vào phản mẫu kiến trúc hố sụt. Tuy nhiên điều quan trọng là phân tích tỉ lệ phần trăm yêu cầu thuộc loại này. Quy tắc 80-20 thường là một phương pháp hay, nên tuân theo để xác định liệu có hay không chúng ta đang rơi vào phản mẫu kiến trúc hố sụt. Thông thường có khoảng 20% yêu cầu được xử lí đơn giản và 80% yêu cầu có một số logic nghiệp vụ liên quan đến yêu cầu đó. Tuy nhiên nếu chúng ta thấy rằng tỉ lệ này bị đảo ngược và phần lớn các yêu cầu của chúng ta là quá trình xử lí đơn giản, chúng ta có thể cân nhắc một số tầng trong kiến trúc là mở.

    Một điểm khác cần cân nhắc với mẫu kiến trúc phân lớp là nó có xu hướng thích ứng với các ứng dụng nguyên khối, ngay cả khi chúng ta tách tầng trình diễn và tầng nghiệp vụ thành các đơn có thể triển khai riêng biệt. Mặc dù điều này có thể không phải là vấn đề đáng lo ngại đối với một số ứng dụng nhưng nó đặt ra một số vấn đề tiềm ẩn về triển khai, độ bền và độ tin cậy chung, hiệu suất và khả năng mở rộng.

    Phân tích kiến trúc phân tầng

    Dưới đây là bảng đánh giá và phân tích về các đặc điểm kiến trúc phổ biến của kiến trúc phân tầng. Các đánh giá cho từng đặc điểm dựa trên xu hướng tự nhiên của các đặc điểm đó, điển hình như là khả năng triển khai cũng như là mức độ phổ biến của mẫu kiến trúc này.

    Đặc điểm Đánh giá
    Tính linh hoạt tổng thể
    Dễ triển khai
    Khả năng kiểm thử
    Hiệu năng
    Khả năng mở rộng
    Dễ phát triển

    Tính linh hoạt tổng thể

    Tính linh hoạt tổng thể là khả năng đáp ứng nhanh chóng với một môi trường thay đổi liên tục. Trong khi thay đổi được cô lập thông qua tầng cô lập, nó vẫn cồng kềnh và tốn thời gian để thực hiện các thay đổi trong kiến trúc này bởi vì bản chất nguyên khối của hầu hết các triển khai cũng như sự liên kết chặt chẽ của các thành phần thường được tìm thấy với mẫu kiến trúc này.

    Dễ triển khai

    Tùy thuộc vào cách chúng ta triển khai mẫu này, khả năng triển khai có thể trở thành một vấn đề, đặc biệt đối với các ứng dụng lớn. Một thay đổi nhỏ đối với một thành phần có thể yêu cầu triển khai lại toàn bộ ứng dụng (hoặc một phần lớn của ứng dụng), dẫn đến việc triển khai cần được lập kế hoạch, được lên lịch và thực hiện ngoài giờ hoặc vào cuối tuần. Như vậy, mô hình này không dễ thích ứng với việc triển khai liên tục, tiếp tục giảm xếp hạng tổng thể cho triển khai.

    Khả năng kiểm thử

    Trong kiến thúc này, các thành phần trong thuộc vào một tầng cụ thể, các tầng khác có thể được mô phỏng hoặc khai thác, giúp cho kiến trúc này tương đối dễ kiểm thử. Một nhà phát triển có thể giả lập một thành phần trình bày hoặc màn hình để cô lập thử nghiệm trong một thành phần nghiệp vụ, cũng như mô phỏng tầng nghiệp vụ để kiểm tra chức năng màn hình nhất định.

    Hiệu năng

    Mặc dù đúng là một số kiến trúc phân tầng có thể hoạt động tốt, nhưng kiến trúc này không phù hợp với các ứng dụng hiệu năng cao do tính không hiệu quả của việc phải đi qua nhiều tầng của kiến trúc để đáp ứng yêu cầu nghiệp vụ.

    Khả năng mở rộng

    Do xu hướng triển khai nguyên khối và liên kết chặt chẽ của kiến trúc này, các ứng dụng được xây dựng bằng cách sử dụng mẫu kiến trúc này thường khó mở rộng quy mô. Chúng ta có thể mở rộng quy mô kiến trúc phân tầng bằng cách tách các tầng thành các triển khai vật lý riêng biệt hoặc sao chép toàn bộ ứng dụng thành nhiều nút, nhưng nhìn chung mức độ chi tiết quá rộng, khiến việc mở rộng quy mô trở nên tốn kém.

    Dễ phát triển

    Tính dễ phát triển nhận được điểm tương đối cao, chủ yếu là do mô hình này quá nổi tiếng và không quá phức tạp để thực hiện. Bởi vì hầu hết các công ty phát triển ứng dụng bằng cách tách các bộ kỹ năng theo tầng (trình diễn, nghiệp vụ, cơ sở dữ liệu), kiến trúc này trở thành lựa chọn tự nhiên cho hầu hết việc phát triển ứng dụng kinh doanh. Mối liên hệ giữa cơ cấu tổ chức và truyền thông của công ty với cách thức phát triển phần mềm được vạch ra là cái được gọi là định luật Conway. Bạn có thể Google "Conway’s law" để có thêm thông tin về mối tương quan hấp dẫn này.

    Tài liệu tham khảo

    • Software Architecture Patterns

  • Kiến trúc phân tầng (Layered Architecture) (Phần 1)

    Kiến trúc phân tầng (Layered Architecture) (Phần 1)

    Kiến trúc phân tầng(hay còn được gọi là kiến trúc n-tier) là kiến trúc phổ biến nhất. Kiến trúc này được xem là chuẩn không chính thức cho các ứng dụng Java EE và được biết đến rộng rãi bởi hầu hết kiến trúc sư, nhà thiết kế và nhà phát triển. Kiến trúc này quen thuộc với các cơ cấu tổ chức và truyền thông CNTT truyền thống, được tìm thấy ở hầu hết các công ty khiến nó trở thành một lựa chọn tự nhiên cho các công ty phát triển ứng dụng doanh nghiệp.

    Giới thiệu kiến trúc phân tầng

    Các thành phần bên trong kiến trúc phân tầng được tổ chức thành các tầng nằm ngang, mỗi tầng thực hiện một vai trò cụ thể trong ứng dụng, ví dụ như tầng trình diễn(presentation) hay tầng nghiệp vụ (business). Mặc dù kiến trúc phân tầng không qui định số lượng hay các loại tầng phải tồn tại, hầu hết các kiến trúc phân tầng bao gồm 4 tầng: tầng trình diễn, tầng nghiệp vụ, tầng lưu trữ(persistence), tầng cơ sở dữ liệu(database).

    4-tier

    Trong một vài trường hợp tầng nghiệp vụ và tầng lưu trữ có thể được kết hợp thành một tầng nghiệp vụ, đặc biệt khi logic lưu trữ (SQL hay HSQL) được nhúng bên trong các thành phần của tầng nghiệp vụ. Vì vậy với các ứng dụng nhỏ có thể chỉ có ba tầng, ngược lại với các ứng dụng lớn hoặc các ứng dụng có nghiệp vụ phức tạp có thể chứa năm tầng hoặc nhiều hơn.

    Mỗi tầng trong kiến trúc phân tầng có một vai trò và trách nhiệm cụ thể trong ứng dụng. Ví dụ tầng trình diễn sẽ có trách nhiệm xử lí tất cả giao diện người dùng và logic giao tiếp trình duyệt, trái lại tầng nghiệp vụ sẽ chịu trách nhiệm thực thi các quy tắc nghiệp vụ cụ thể. Mỗi tầng trong kiến trúc phân tầng trừu tượng hoá các công việc cần phải hoàn thành để đáp ứng một yêu cầu nghiệp vụ cụ thể. Ví dụ tầng trình diễn không cần phải biết hay lo lắng về việc lấy dự liệu khách hàng như thế nào; nó chỉ cần hiển thị các thông tin đó lên màn hình theo định dạng cụ thể. Tương tự như vậy, tầng nghiệp vụ không cần bận tâm làm thế nào định dạng dữ liệu khách hàng để hiển khị lên màn hình hoặc ngay cả việc dự liệu khách hàng được lưu trữ ở đâu; nó chỉ cần lấy dữ liệu từ tầng lưu trữ, thực hiện logic nghiệp vụ dựa trên dữ liệu đó(tính toán các giá trị hay tổng hợp dữ liệu), và truyền các thông tin đó tới tầng trình diễn.

    Một trong những tính năng mạnh mẹ của kiến trúc phân tầng sự tách biệt các mỗi bận tâm giữa các thành phần. Các thành phần bên trong một tầng cụ thể chỉ giải quyết các logic liên quan đến tầng đó. Ví dụ các thành phần ở tầng trình diễn chỉ giải quyết các logic hiển thị, ngược lại các thành phần cư trú ở tầng nghiệp vụ chỉ giải quyết các logic nghiệp vụ. Việc phân loại các loại thành phần như thế này giúp cho việc xây dựng mô hình trách nhiệm và vai trò có hiệu quả trở nên dễ dàng hơn, đồng thời giúp bạn dễ dàng phát triển, kiểm thử, quản lí, và bảo trì các ứng dụng nhờ vào các giao diện thành phần được mô tả rõ ràng và phạm vi thành phần được giới hạn.

    Tầng đóng(closed layer)

    Chúng ta cùng xem hình dưới đây:

    closed_layer.png

    Mỗi tầng trong kiến trúc này được đánh dấu CLOSED. Đây là một khái niệm rất quan trọng trong kiến trúc phân tầng. Một tầng đóng có nghĩa là một yêu cầu(request) đi từ tầng này sang tầng khác phải đi qua tầng ngay bên dưới nó để đi tới tầng tiếp theo bên dưới tầng đó. Ví dụ một request bắt nguồn từ tầng trình diễn phải đi qua tầng trình diễn sau đó tới tầng lưu trữ trước cuối cùng chốt lại ở tầng cơ sở dữ liệu.

    Tại sao không cho phép tầng trình diễn truy cập trực tiếp đến tầng lưu trữ hay tầng cơ sở dữ liệu?

    Truy cập trực tiếp cơ sở dữ liệu từ tầng trình diễn thì nhanh hơn là thông qua một loạt các tầng không cần thiết chỉ để lấy ra hay lưu lại thông tin cơ sở dữ liệu. Câu trả lời cho câu hỏi này nằm ở một khái niệm tầng cô lập(layers of isolation).

    Tầng cô lập

    Khái niệm tầng cô lập có nghĩa là những thay đổi được tạo ra trong một tầng của kiến trúc nhìn chung không có tác động hay ảnh hướng đến các thành phần của tầng khác: thay đổi được cô lập trong các thành phần bên trong lớp đó và một lớp nào đó có có thể có liên quan(ví dụ như tầng lưu trữ có chứa SQL). Nếu chúng ta cho phép tầng trình diễn truy cập trực tiếp tới tầng lưu trữ thì những thay đổi được tạo ra với SQL trong tầng lưu trữ sẽ có tác động tới cả tầng nghiệp vụ và tầng trình diễn, do đó tạo ra một ứng dụng liên kết rất chặt chẽ với rất nhiều phụ thuộc chéo giữa các thành phần. Những ứng dụng như thế này quá cứng nhắc và tốn kém khi có thay đổi.

    Khái niệm tầng cô lập cũng có nghĩa là mỗi một tầng độc lập với các tầng khác, do đó chúng biết rất ít hoặc không biết về hoạt động bên trong của các tầng khác trong kiến trúc. Để hiểu được sức mạnh và tầm quan trọng của khái niệm này, chúng ta cùng xem xét nỗ lực tái cấu trúc để chuyển đổi presentation framework từ JSP (Java Server Pages) to JSF (Java Server Faces). Giả sử rằng hợp đồng(model) được sử dụng giữa tầng trình diễn và tầng nghiệp vụ không thay đổi, tầng nghiệp vụ không bị ảnh hưởng bởi việc tái cấu trúc và giữ độc lập hoàn toàn với các loại giao diện người dùng được sử dụng bởi tầng trình diễn.

    Tầng mở

    Tầng đóng tạo điều kiện thuận lợi cho tầng cô lập và do đó giúp cô lập thay đổi trong kiến trúc, đôi khi tầng mở có ý nghĩa đối với các tầng nhất định. Giả sử bạn muốn thêm vào tầng dịch vụ chia sẻ(shared services) chứa các thành phần dịch vụ dùng chung được truy cập bởi các thành phần trong tầng nghiệp vụ(các lớp tiện ích dữ liệu hay chuổi, các lớp kiểm tra và ghi nhật ký). Trong trường hợp này việc tạo ra tầng dịch vụ thường là ý tưởng tốt bởi lẽ về mặt kiến trúc thì nó hạn chế các truy cập vào các dịch vụ chia sẻ đổi với tầng nghiệp vụ(chứ không phải tầng trình diễn). Không có sự tách biệt về tầng, về mặt kiến trúc không có gì hạn chế tầng trình diễn truy cập vào các dịch vụ dùng chung này, gây khó khăn cho việc quản lý hạn chế truy cập.

    Trong ví dụ này, tầng dịch vụ mới sẽ có thể sẽ nằm bên dưới tầng nghiệp vụ để chỉ rằng các thành phần trong tầng dịch vụ này không thể truy cập từ tầng trình diễn. Tuy nhiên, điều này có nghĩa rằng tầng nghiệp vụ lúc này bắt buộc phải đi qua tầng dịch vụ để tới tầng lưu trữ, điều này không có ý nghĩa gì cả. Đây là vấn đề lâu đời của kiến trúc phân tầng. Để giải quyết vấn đề này chúng ta tạo ra tầng mở trong kiến trúc.

    open_layer.png

    Các tầng dịch vụ trong trường hợp này được đánh dấu là OPEN, có nghĩa là các yêu cầu được cho phép đi qua tầng mở và đi trực tiếp tới tầng bên dưới. Trong ví dụ trên vì tầng dịch vụ là tầng mở nên tầng nghiệp vụ bây giờ được phép bỏ qua nó và đi trực tiếp tới tầng lưu trữ, điều này hoàn toàn hợp lí.

    Chúng ta có thể tận dụng khái niệm về tầng mở và đóng để xác định mối quan hệ giữa các tầng trong kiến trúc và luồng yêu cầu cũng như cung cấp cho nhà thiết kế, nhà phát triển các thông tin cần thiết để hiểu các hạn chế truy cập tới các tầng khác nhau trong kiến trúc. Việc không thể xác định chính xác các tầng nào trong kiến trúc là mở hay đóng và tại sao thường dẫn tới các cấu trúc liên kết chặt chẽ và dễ vỡ, chúng rất khó để kiểm thử, bảo trì và triển khai.

    Tài liệu tham khảo

    • Software Architecture Patterns
  • Architecture pattern: Clean Swift

    Architecture pattern: Clean Swift

    Xin chào mọi người! Lại là DaoNM2 đây! Tiếp tục với series về Architecture pattern hôm nay mình sẽ giới thiệu cho các bạn một kiến trúc khá mới so với các mẫu kiến trúc hiện tại đó là Clean Swift (VIP). Trước đây mình đã có cơ hội tiếp cận với kiến trúc này, khi tham gia vào việc xây dựng một ứng dụng rất lớn cho một công ti rất nổi tiếng về ô tô xe máy ở Việt Nam. Khi đó mình cũng đã tích luỹ được một số kinh nghiệm về kiến trúc này, vì vậy mình muốn chia sẻ với các bạn một số thông tin cũng như kinh nghiệm mà mình đã tích luỹ được khi làm việc với mẫu kiến trúc này.

    Đầu tiên nếu bạn là người mới hoặc bạn mới tìm hiều về các mẫu kiến trúc trong lập trình bao giờ đây là bài đầu tiên bạn đọc, thì để có thể hiểu kiến trúc này tốt hơn thì bạn có thể tham khảo các bài viết về các mẫu thiết kế trước khi tiếp tục ở link dưới đây:

    Bối cảnh hình thành

    Clean swift lần đầu tiên được giới thiệu bởi Raymond Law trên website clean-swift.com của anh ấy. Ý tưởng hình thành mẫu kiến trúc này là do anh ấy đã quá chán với các vấn đề của MVC(một mẫu kiến trúc mà Apple khuyên dùng) vì vậy anh ấy đã nghĩ ra Clean Swift để giải quyết các vấn đề mà các mẫu kiến trúc trước đây chưa làm được. Clean Swift được Raymond Law xây dựng dựa trên Clean Architecture của Uncle Bob.

    Clean Swift là gì?

    Clean Swift là một mẫu kiến trúc xây dựng dựa trên Clean Architecture của Uncle Bob để áp dụng cho việc xây dựng các ứng dụng iOS và MacOS.

    Trong Clean Swift Architecture pattern thì tất cả logic của ứng dụng sẽ được chia đều ra 3 thành phần chính của nó là View controller, Interactor và Presenter. Mỗi phần sẽ đảm nhiệm một số logic cụ thể, và chúng liên lạc với nhau bằng các liên kết 1 chiều, vì vậy source code của bạn sẽ luôn luôn đi theo một chiều chứ không đa chiều như các architecture pattern khác.

    Khi ứng dụng Clean Swift vào trong dự án của bạn, nó sẽ được cấu trúc theo từng màn hình của ứng dụng(scenes).

    Các thành phần của Clean Swift

    Mẫu kiến trúc Clean Swift được cấu tạo bởi các phần như sau:

    • View
    • View Controller
    • Router
    • Presenter
    • Interactor
    • Worker(optional)
    • Model(optional)
    Clean Swift architecture pattern

    Clean Swift gồm 3 phần chính là ViewController, Presenter và Interactor. 3 phần này có liên kết 1 chiều và tạo thành 1 vòng tròn. Khi View controller nhận được request nó sẽ gọi sang Interactor để nó xử lí logic, khi xong logic Interactor sẽ gửi dữ liệu sang bên Presenter để nó thực hiện format lại dữ liệu rồi trả về cho ViewController làm nhiệm vụ update lên View cho người dùng. Các thành phần này sẽ được kết nối với nhau bằng protocol.

    View

    Là các thành phần nằm trong UIKit hoặc bất kể thứ gì liên quan tới UI ví dụ như: storyboard, xib, UIView, UIControl …

    View Controller

    Định nghĩa các màn hình(scenes), nó có thể chứa 1 hoặc nhiều View

    Nó sẽ giữ các instances của Interactor và Router

    Là nơi nhận các tương tác của người dùng và gọi đến Interactor hoặc Router để xử lý, nó cũng nhận output của Presenter làm input và truyền nó lên view để hiển thị cho người dùng.

    Interactor

    Chứa các business logic của màn hình

    Giữ instance của Presenter và các Workers(nếu có)

    Nhận thông tin input từ ViewController và xử lý hoặc yêu cầu Worker làm việc để truyền kết quả sang cho Presenter

    Interactor sẽ không được import UIKit để đảm bảo source không có liên kết trực tiếp với View

    Presenter

    Giữ một tham chiếu yếu đến View Controller để truyền dữ liệu sang View Controller

    Là nơi xử lý logic hiển thị, khi nhận được input từ Interactor nó sẽ thực hiện format lại dữ liệu và truyền sang cho ViewController để nó hiển thị thông tin cho người dùng.

    Worker

    Là nơi được coi là trung tâm dữ liệu, nó sẽ thực hiện các nhiệm vụ liên quan tới việc lấy dữ liệu từ API hoặc LocalDB

    Là thành phần phụ nên ở các màn hình đơn giản không có tương tác với dữ liệu chúng ta có thể bỏ qua worker

    Router

    Nó giữ một tham chiếu yếu tới View Controller, nhằm mục đích tránh việc tham chiếu lẫn nhau(retain cycles) dẫn đến không thể release các đối tượng này khi nó không còn được sử dụng -> Lack memory.

    Router sinh ra để giảm tải công việc cho View Controller nó sẽ làm nhiệm vụ điều hướng trong ứng dụng.

    Model

    Nó là nơi định nghĩa các đối tượng cho ứng dụng, nó chỉ làm nhiệm vụ định nghĩa các đối tượng và không có xử lí logic hay liên kết trực tiếp với các thành phần khác của kiến trúc.

    Các đối tượng trong model sẽ được khai báo theo value type(struct, enum)

    Tương tự như Worker, một số màn hình đơn giản không tương tác với dữ liệu sẽ không cần đến Model

    Ưu điểm

    • Dễ maintain, fixbugs vì liên kết 1 chiều giữa các thành phần
    • Hỗ trợ viết Unit test một cách dễ dàng
    • Viết các phương thức ngắn hơn với trách nhiệm duy nhất
    • Tách được business logic sang cho Interactor xử lí
    • Có thể tái sử dụng các Workers và Services
    • Có thể áp dụng được cho các dự án lớn để giảm tình trạng conflict khi merge source.

    Nhược điểm

    • Quá nhiều các protocol với các nhiệm vụ riêng biệt, làm cho việc đặt tên trở nên khó khăn và không cẩn thận sẽ gây khó hiểu cho người đọc
    • Kích thước ứng dụng lớn do chứa nhiều protocol và nhiều file
    • Cần thời gian để cho các thành viên dự án có thể hiểu và tuân theo

    Tổng kết

    Clean swift là một mẫu kiến trúc không phổ biến như các mẫu khác, tuy nhiên ưu điểm của nó mang lại là rất lớn. Để vận hành các dự án lớn có yêu cầu viết Unitest chúng ta có thể coi Clean Swift là một trong những ứng cử viên sáng giá. Mình hi vọng bài viết có thể giúp các bạn có thêm kiến thức về mẫu kiến trúc Clean Swift và giúp các bạn có thể chọn được mẫu kiến trúc ưng ý cho những dự án sắp tới.

    Nếu các bạn muốn biết thêm nhiều thông tin hơn về Clean Swift Architecture Pattern thì các bạn có thể tham khảo tại link sau: Clean Swift

  • Architecture Pattern: VIPER trong iOS

    Architecture Pattern: VIPER trong iOS

    Xin chào các bạn, lại là DaoNM2 đây! Để tiếp tục series về Architecture patterns thì hôm nay mình xin giới thiệu cho các bạn một mẫu kiến trúc được sử dụng khá nhiều khi phát triển các ứng dụng di động đó là VIPER.

    VIPER là gì?

    VIPER là một mẫu kiến trúc để phát triền phần mềm, nó được sử dụng khá nhiều khi xây dựng các ứng dụng di động trên ngôn ngữ lập trình Swift. Nó được xây dựng dựa trên Clean Design Architecture. Các Modules trong VIPER được định hướng theo Protocol và mỗi chức năng, các thuộc tính input và output được thực hiện bằng các bộ quy tắc giao tiếp cụ thể.

    Các thành phần chính của VIPER architecture pattern

    VIPER là viết tắt của các chứ cái đầu trong các thành phần của nó, nó bao gồm View, Interactor, Presenter, Entity và Router. Các thành phần này sẽ tương tác với nhau như sơ đồ dưới đây:

    View

    Bao gồm các thành phần trong UIKit và ViewController, nó là nơi để hiển thị nội dung cho người dùng và nhận các tương tác từ người dùng sau đó gửi cho presenter để xử lí tiếp logic hiển thị. Trong mẫu kiến trúc này Presenter là tầng duy nhất có liên kết với View.

    Interactor

    Là nơi xử lý business logic của ứng dụng, nó sẽ thao tác với Entity, model, API fetcher và datastore. Khi nhận được request từ Presenter lúc này Interactor sẽ thực hiện logic để lấy dữ liệu tương ứng và trả về cho presenter.

    Trong VIPER mỗi một Interactor sẽ tương ứng với một Use case, nó tách biệt hoàn toàn với View vì vậy khả năng kiểm thử độc lập trên Interactor khá dễ dàng.

    Presenter

    Là nơi xử lý logic hiển thị của ứng dụng, khi nhận được request thay đổi hoặc hiển thị thông tin từ View nó sẽ thực hiện logic tương ứng để yêu cầu Interactor trả về data. Sau khi nhận được data nó sẽ format lại dữ liệu và trả về cho View để hiển thị chúng lên màn hình. Khi nhận được yêu cầu di chuyển màn hình Presenter sẽ thực hiện call Router để nó làm nốt nhiệm vụ điều hướng

    Entity

    Đây là các Data model, nó có nhiệm vụ tương tác với Interactor để trả dữ liệu về cho Presenter.

    Router

    Là nơi xử lí luồng của ứng dụng, nó làm nhiệm vụ điều hướng ứng dụng đến nơi mà người dùng cần. Khi Presenter nhận yêu cầu chuyển màn hình từ View, nó sẽ thực hiện logic hiển thị và thực hiện tương tác với Router để xử lí di chuyển luồng đúng với yêu cầu của View.

    Ưu điểm

    VIPER được chia nhỏ thành nhiều phần, các phần đảm nhiệm các vai trò và nhiệm vụ cố định, các thành phần tương tác với nhau dựa trên các quy định cụ thể vì vậy nó có khá nhiều ưu điểm

    • Các nhiệm vụ được chia đều ra cho các thành phần vì vậy việc maintain không còn quá rắc rối.
    • Việc kiểm thử (Unit test) cũng trở nên dễ dàng hơn vì giờ đây các thành phần đã được chia nhỏ và không liên kết chặt chẽ với View
    • Cấu trúc source trở nên dễ hiểu và rõ ràng hơn vì nó được chia theo từng use case và các phần được chia nhiệm vụ và trách nhiệm rõ ràng
    • Không gặp phải trường hợp một file có nội dung quá dài, vì vậy việc đọc source code của người cũng trở nên dễ hiểu hơn
    • Khá là hữu dụng với các ứng dụng lớn với team size lớn
    • Dễ dàng để mở rộng và bảo trì, các developer có thể đồng thời làm việc trên nó một cách trơn tru
    • Giảm số lượng conflict khi merge source code

    Nhược điểm

    • Do có nhiều thành phần và tương tác với nhau nên số file quản lý sẽ nhiều hơn so với các mẫu kiến trúc khác
    • Không dễ sử dụng cho người mới vì có nhiều ràng buộc và quy tắc cho từng phần, vì vậy cần thời gian để các member có thể tìm hiểu và thích nghi với mẫu kiến trúc này.
    • Một số thư viện bên thứ 3 không hỗ trợ kiến trúc này, vì vậy nếu không có lựa chọn nào khác lúc này nếu áp dụng thư viện vào ứng dụng nó sẽ phá vỡ kiến trúc ở các tính năng mà sử dụng thư viện này.

    Tổng kết

    Như mình đã phân tích ở trên VIPER có rất nhiều ưu điểm vì vậy nó rất đáng để các bạn tìm hiểu và sử dụng cho các dự án sắp tới. Tuy nhiên theo mình thì mẫu kiến trúc VIPER chỉ nên sử dụng cho những ứng dụng có kích thước vừa và lớn thì nó mới phát huy được tối đa sự hiệu quả. Đối với các dự án nhỏ nếu sử dụng VIPER architecture pattern thì nó lại trở nên quá cồng kềnh và không cần thiết.

    Mình hi vọng bài viết mình chia sẻ sẽ giúp các bạn có thêm lựa chọn khi bắt đầu một dự án mới!

    Chúc các bạn thành công!

  • Architecture Pattern: MVVM trong iOS

    Architecture Pattern: MVVM trong iOS

    Chào các bạn, để tiếp tục series về Architecture pattern thì hôm nay mìn sẽ giới thiệu đến một mô hình có thể giải quyết được một số nhược điểm của các mô hình cũ như MVC, MVP.

    Nếu các bạn chưa tiếp cận hoặc chưa tìm hiều về các Architecture Pattern bao giờ thì có thể xem lại các bài viết của mình về MVC hoặc MVP tại đây:
    iOS Architecture Patterns: Cocoa MVC
    MVP Architecture Pattern và biến thể MVP-C
    Để khi đi vào bài viết này chúng ta sẽ dễ dàng hiểu được nội dung bài viết truyền tải.

    Lịch sử hình thành và phát triển

    MVVM được viết đầy đủ là Model View ViewModel, MVVM là một biến thể của mẫu thiết kế Presentation Model của Martin Fowler. Nó được sáng lập ra bởi các kiến trúc sư của Microsoft tên là Ken Cooper và Ted Peters, nó đặc biệt được sinh ra để làm đơn giản việc lập trình hướng sự kiện (event-driven programming). MVVM được tích hợp vào Windows Presentation Foundation (WPF) (hệ thống đồ họa .NET của Microsoft) và Silverlight, dẫn xuất ứng dụng Internet của WPF. John Gossman, một kiến ​​trúc sư Microsoft WPF và Silverlight, đã công bố MVVM trên blog của mình vào năm 2005.

    MVVC là gì?

    MVVC là một mẫu kiến trúc giúp tách biệt source code của bạn ra thành nhiều thành phần khác nhau. Nó giúp code của bạn có các thành phần độc lập, giúp cho quá trình phát triển và kiểm thử ứng dụng trở nên rõ dàng và dễ dàng hơn.

    Cấu tạo của MVVM

    MVVM gồm 3 phần chính là Model, View và ViewModel.

    MVVM

    Model

    Là nơi chứa dữ liệu và xử lí business logic, model sẽ thực hiện các công việc như lưu trữ các data được lấy về từ API, local storage, v.v. Nó độc lập so với View và tương tác với View thông qua ViewModel.

    View

    Là nơi hiển thị giao diện cho người dùng, nhận các sự kiện từ người dùng, xử lí và gửi các yêu cầu của người dùng cho ViewModel xử lí. View trong iOS thì thông thường là các thành phần của UIKit, storyboard, xib …, ở MVVM trong iOS thì View bao gồm cả các View Controller, nó sẽ là thành phần cài đặt cho View và gửi và nhận thông tin từ ViewModel.

    ViewModel

    Là nơi xử lí các logic hiển thị(presentation logic), nó là cầu nối giữa View và Model. ViewModel sẽ nhận yêu cầu từ View và lấy dữ liệu từ model về xử lí sau đó trả lại cho View thứ mà nó cần để hiển thị lên màn hình cho người dùng.

    Trong khi MVC thì Controller, MVP thì có Presenter làm trung gian giữa View và Model. Ở MVVM thì ViewModel cũng tương tự, nó là thành phần trung gian giúp kết nối View với Model.

    Ưu điểm của MVVM

    • Vì MVVM là mô hình nâng cấp của MVC, cho nên nó giúp app vẫn duy trì cấu trúc của mô hình MVC và bao gồm các ưu điểm của MVC
    • Giảm tải lượng code chứa trong View và View Controller.
    • Khi đó View và View Controller trở nên đơn giản hơn khi những logic.
      Ví dụ như logic về quy định cách hiển thị của dữ liệu, được chuyển hết sang ViewModel. Điều này khiến cho code trở nên dễ hiểu và dễ maintain hơn.
    • Sự liên lạc giữa các thành phần trong mô hình rõ ràng, khiến nó hoạt động tốt hơn với cơ chế binding dữ liệu.
    • Có thể thực hiện UnitTest lên tầng ViewModel.
    • Nhiệm vụ được chia đều cho các tầng

    Nhược điểm của MVVM

    • Nhiều file nên source code lại nhiều thêm
    • Tương tác giữa các thành phần phức tạp hơn các mẫu kiến trúc khác như MVC, MVP vì vậy người mới khó tiếp cận và thực hiện hơn.
    • Rắc rối trong việc phản hồi lại yêu cầu hơn so với các mẫu kiến trúc khác
    • Đối với nhưng dự án nhỏ thì nó lại quá cồng kềnh để thực hiện

    Tổng kết

    MVVM là một mẫu kiến trúc rất tốt khi bạn triển khai những ứng dụng có kích thước vừa và lớn, nó hỗ trợ UnitTest khá hiệu quả. Trong MVVM thì nhiệm vụ được chia đều cho các tầng vì vậy sẽ không quá khó để quản lý source code. Mình hi vọng bài viết giúp các bạn có thể dễ dàng hơn khi chon mẫu kiến trúc cho các dự án mới. Chúc các bạn thành công!

  • MVP Architecture Pattern và biến thể MVP-C

    MVP Architecture Pattern và biến thể MVP-C

    Là một Developer, chắc hẳn các bạn đã trải qua nhiều dự án khác nhau. Thông thường khi bạn càng làm nhiều dự án bạn càng có nhiều cơ hội tiếp cận đến các loại Architecture pattern khác nhau như MVC, MVP, MVVM, VIPPER, … 

    Sau khi chinh chiến ở các dự án lớn nhỏ khác nhau mình cũng tích luỹ được một chút kiến thức về MVP Architecture pattern, vì vậy mình muốn viết một bài để chia sẻ một số kiến thức nho nhỏ mà mình đã học được về MVP cho những bạn chưa có cơ hội làm việc với MVP Architecture pattern.

    Lịch sử hình thành và phát triển

    MVP là viết tắt của Model View Presenter, nó bắt nguồn từ đầu những năm 1990 tại Talligent, một liên doanh của Apple, IBM và Hewlett-Packard. MVP là mô hình lập trình cơ bản để phát triển ứng dụng trong môi trường CommonPoint dựa trên C++ của Taligent. Sau này nó đã được Taligent chuyển sang Java.

    Đến năm 1998 thì Taligent giải thể, Andy Bower và Blair McFlashan của Dolphin Samlltalk đã điều chỉnh MVP để tạo cơ sở cho Smalltalk của họ.

    Đến năm 2006 thì Microsoft cũng bắt đầu kết hợp MVP vào tài liệu và ví dụ về lập trình giao diện người dùng trong .NET Framework.

    Đến nay thì MVP được sử dụng khá là rộng rãi vì những lợi ích mà nó đem lại cho các lập trình viên. Ngoài ra MVP cũng có rất nhiều biến thể để cải thiện những nhược điểm của nó.

    MVP là gì?

    MVP là một mẫu kiến trúc giao diện người dùng(user interface architecture pattern) được thiết kế để tạo điều kiện thuận lợi cho Automated Unit Testing(Chạy Unit Test tự động) và cải thiện việc phân tách các thành phần trong trình bày logic(presentation logic).

    MVP sinh ra dựa trên kiến trúc MVC, nó hướng tới mục tiêu cải thiện kiến trúc MVC.

    MVP được thể hiện băng hình ảnh sau:

    Model: là một interface xác định dữ liệu được hiển thị hoặc dữ liệu này được thực hiện trong giao diện người dùng.

    View: là một interface thụ động dùng để hiện thị dữ liệu của Model và định hướng các lệnh người dùng (events) tới Presenter để Presenter hành động dựa trên các dữ liệu đó.

    Presenter: hành động theo Model và View. Presenter lấy dữ liệu từ kho lưu trữ (Model), sau đó định dạng dữ liệu và hiển thị lên View.

    Ưu điểm của MVP

    Như đã nói ở trên do MVP được xây dựng dựa trên kiến trúc MVC nên nó sẽ có các ưu điểm tương tự như MVC. Các bạn có thể xem thêm về MVC ở bài viết sau: iOS Architecture Patterns: Cocoa MVC

    Mục đích cao cả của MVP sinh ra là để cải thiện những nhược điểm của kiến trúc MVC vì vậy nó giúp giảm tải lượng lớn logic nằm ở tầng Model so với mô hình MVC

    Kiến trúc MVP có tầng Presenter chuyên để xử lý các logic hiển thị, nó là thành phần trung gian tương tác với View và Model qua interface nên nó có thể viết Unit testing một cách dễ dàng.

    Nhược điểm của MVP

    Cũng như MVC, kiến trúc MVP cũng có những nhược điểm. Nhược điểm lớn nhất của MVP là càng về sau Presenter của MVP sẽ càng phình to nếu logic được thêm mới. Khí đó bạn sẽ rất khó để chia nhỏ khi presenter quá lớn.

    Biến thể MVP-C trong iOS

    Khái niệm Coordinator lần đầu tiên được đưa ra bởi Khanlou vào năm 2015, nó là một giải pháp để xử logic luồng cho View Controller.

    Dựa trên điều này kiến trúc MVP-C được ra đời với C là Coordinator làm nhiệm vụ xử lý luồng cho ứng dụng và các tầng cũ là Model, View và Presenter vẫn giống như MVP được mô tả ở trên.

    Ưu điểm của MVP-C

    View controller có thể tập trung vào mục tiêu chính của chúng. Giúp phân chia rõ ràng vai trò của View.

    Giúp giảm tải các logic trên các tầng khác, ta có thể đưa một số logic như phân luồng di chuyển màn hình từ presenter vào coordinator để giúp presenter đỡ trở nên cồng kềnh khi có quá nhiều logic. Nó đã cải thiện được nhược điểm của kiến trúc MVP truyền thống.

    Ngoài ra Coordinator cũng được ứng dụng vào các kiến trúc khác như MVC để tạo ra MVC-C và MVVM tạo ra MVVM-C.

    Tổng kết

    Đó là những kiến thức mà mình đã tích luỹ được khi làm việc với các dự án được thực hiện theo kiến trúc MVP. Mình hi vọng nó sẽ giúp ích cho các bạn khi cần thiết.

  • Software Architecture: Bắt đầu từ đâu? –  Phần cuối: Hard skills và tổng kết

    Software Architecture: Bắt đầu từ đâu? – Phần cuối: Hard skills và tổng kết

    Như mình đã nói ở phần đầu hard skill của developer và software architect khá rõ ràng về mặt tiếp cận, có nhiều nguồn tài liệu để đọc mình có thể list ra ở đây

    • Domain-Driven Design: Tackling Complexity in the Heart of Software
    • Software Architecture for Developers
    • Software Systems Architecture
    • Design It! – From Programmer to Software Architect
    • Software Architecture in Practice
    • Design Patterns – Elements of Reusable Object-Oriented Software
    • Domain Driven Design
    • Systems Architecting: Creating & Building Complex Systems
    • Systemantics: How Systems Work and Especially How They Fail
    • Release It!: Design and Deploy Production-Ready Software (Pragmatic Programmers)
    • The Clean Architecture
    • Patterns of Enterprise Application Architecture
    • The Art of Scalability: Scalable Web Architecture, Processes, and Organizations for the Modern Enterprise
    • Martin Fowler – Patterns of Enterprise Application Architecture (2002)

    Trong đó quyển của Martin Fowler khá đầy đủ và chi tiết.

    Vậy chúng ta cần đọc bao nhiêu sách, biết bao nhiêu loại pattern? Câu trả lời luôn là càng nhiều càng tốt

    Chọn architecture cho dự án

    Hay nói đúng hơn là khi bắt đầu dự án, chúng ta sẽ chọn kiến trúc nào: microservice, event-driven hay layered theo cách truyền thống. Đấy là câu hỏi thường gặp và chả có gì sai khi hỏi như vậy cả vì ai cũng biết rằng phần mềm mà đưa ra thị trường kiểu gì chẳng được thiết kế theo kiến trúc nào đó. Tuy nhiên câu trả lời lại thường khiến chúng ta bất ngờ

    Big bang architecture

    Nghe giống như Big Bang Model (một loại mô hình phát triển phần mềm), và đúng là như thế.

    Khi dự án mới bắt đầu, chúng ta chưa hiểu gì về hệ thống cả, chỉ nên thiết kế và làm kiến trúc chỉ những gì cần thiết. Ví dụ chúng ta chỉ nên quyết định có thể cần integrate 2 hệ thống với nhau mà chưa cần quyết định xem sẽ làm việc đó như nào. Về cơ bản, chúng ta cần tiến hóa architecture theo dự án: những đều chúng ta biết thêm về hệ thống.

    Rất lạ là yêu cầu phần mềm, công nghệ có thể sử dụng và đã sử dụng và business của công ty cũng như khách hàng cần thay đổi hằng ngày, nhưng chúng ta thường nghĩ rằng architecture cần phải cố định.

    Thường thì mình sẽ chọn một architecture vừa đủ dùng trong một thời gian phù hợp với dự án và đội phát triển (tất nhiên không phải là phần mềm kiểu enterprise rồi)

    Và gần như kiến trúc này sẽ được cải tiến cùng với CI/CD trong phần trước đó, nếu kiến trúc đó có vấn đề khi tích hợp với Pipeline thì có nghĩa rằng kiến trúc hoặc Pipeline hoặc cả 2 đều có vấn đề và chúng cần được cải tiến.

    Một dự án cần phát triển một ứng dụng như Tiki chẳng hạn, nhưng Architect lại được bắt đầu với multi tier truyền thống và bạn sẽ có một monolithic web application, ai cũng sẽ thấy rằng, khi bạn cần sửa dù chỉ một phần nhỏ trong header của request, bạn sẽ cần deploy lại cả hệ thống, điều đó không sai nhưng rõ ràng có vấn đề. Điều tất yếu xảy ra kiến trúc sẽ được cải tiến.

    Gần như hiện tại trong quá trình làm dự án, chúng ta có thể thấy rằng nếu như hệ thống có hình dáng giống với một hệ thống nào đó trong quá khứ thì chúng ta sẽ gần như kế thừa và giữ nguyên, trong khi đó các hệ thống chưa xuất hiện bao giờ thì sẽ được quyết định bởi trực giác của Architect trong một quá trình khám phá có kiểm soát.

    Các quyết định này có thể đúng, sai, tăng tiến độ hoặc thậm chí phá hỏng mọi thứ và chúng ta phải rework.

    Khi đó chúng ta có một thứ gọi là Accidental Architecture, tuy nhiên, điều này chưa hẳn đã xấu vì gần như chúng ta không thể tránh khỏi trong quá trình làm dự án và xây dựng hệ thống. Chỉ khi bắt đầu biến những kiến trúc ngẫu nhiên này thành những kiến trúc có chủ đích, chúng ta mới nâng cao hiểu biết của mình về kiến trúc phần mềm.

    Final words

    Có lẽ serires này sẽ dừng ở đây, mình sẽ cố gắng sắp xếp lại một số bài viết liên quan đến chủ đề này trong nhưng bài viết khác.

    THE END.