Month: January 2023

  • Hướng dẫn migrate cơ sở dữ liệu sử dụng Flyway trong ứng dụng Spring Boot

    Hướng dẫn migrate cơ sở dữ liệu sử dụng Flyway trong ứng dụng Spring Boot

    Với hầu hết các dự án việc quản lý các phiên bản database schema là vô cùng quan trọng. Phương pháp thực hiện migrate database tốt sẽ giúp tất cả thành viên dự án dễ dàng đồng bộ môi trường phát triển cũng như là triển khai database schema lên các môi trường khác nhau. Trong bài viết này chúng ta sẽ cùng tìm hiểu phương pháp migrate database schema sử dụng Flyway trong ứng dụng Spring Boot.

    Khởi tạo ứng dụng Spring Boot

    Chúng ta sẽ sử dụng Spring Initializr để khởi tạo ứng dụng Spring Boot. Cấu trúc dự án sẽ giống như sau:

    ├── HELP.md
    ├── README.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── app
    │   │   │       └── demo
    │   │   │           └── DemoApplication.java
    │   │   └── resources
    │   │       ├── application.properties
    │   │       ├── db
    │   │       │   └── migration
    │   │       │       └── V20221124103000__Initial.sql
    │   │       ├── static
    │   │       └── templates
    │   └── test
    │       ├── java
    │       │   └── app
    │       │       └── demo
    │       │           └── DemoApplicationTests.java
    │       └── resources
    

    MySQL

    Chúng ta sẽ sử dụng docker để container chạy MySQL.

    docker run --name demo -e MYSQL_ROOT_PASSWORD=demo@123 -p 3306:3306 -d mysql --lower_case_table_names=1
    

    Chúng ta có thể sử dung MySQL Workbench để kiểm tra database MySQL đã chạy hay chưa.

    Thư viện Maven

    Để sử dụng Flyway chúng ta thêm thư viện phụ thuộc sau:

        <dependency>
          <groupId>org.flywaydb</groupId>
          <artifactId>flyway-mysql</artifactId>
        </dependency>
    

    Trong bài viết này chúng ta sẽ dùng Maven plugin để thực hiện migrate database, chúng ta cần cấu hình plugin này trong pom.xml như sau:

          <plugin>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-maven-plugin</artifactId>
            <configuration>
              <url>jdbc:mysql://localhost:3306/demo?createDatabaseIfNotExist=true</url>
              <user>root</user>
              <password>demo@123</password>
            </configuration>
          </plugin>
    

    Định nghĩa phiên bản migration

    Mặc định Flyway sẽ đọc các tệp SQL trong thư mục resources/db/migration. Tên tệp được định nghĩa theo tài liệu Versioned Migrations.

    Mỗi phiên bản migration có phiên bản, thông tin mô tả phiên bản và checksum. Các phiên bản phải là duy nhất. Thông tin phiên bản là thông tin thuần khiết gợi nhớ cái gì được thực hiện trong phiên bản đó. Checksum được sử dụng để phát hiện các thay đổi ngẫu nhiên. Các phiên bản migration được áp dụng theo một thứ tự nhất định. Các định dạng phiên bản có thể sử dụng như sau:

    • 1
    • 001
    • 5.2
    • 1.2.3.4.5.6.7.8.9
    • 205.68
    • 20130115113556
    • 2013.1.15.11.35.56
    • 2013.01.15.11.35.56

    Trong nội dung bài viết này chúng ta định nghĩa một phiên bản V20221124103000__Initial.sql với nội dung như sau:

    CREATE TABLE IF NOT EXISTS hibernate_sequence (
      sequence_name VARCHAR(128) NOT NULL,
      next_val INT NOT NULL
    );
    
    DROP TABLE IF EXISTS PRODUCT;
    
    CREATE TABLE PRODUCT (
      ID BIGINT PRIMARY KEY AUTO_INCREMENT
      , NAME VARCHAR(255)
      , CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP
      , UPDATED_AT DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
      , DELETED_AT DATETIME DEFAULT NULL
      , CONSTRAINT uk_product_name UNIQUE(name)
    );
    
    DROP TABLE IF EXISTS `ORDER`;
    
    CREATE TABLE `ORDER` (
      ID BIGINT PRIMARY KEY AUTO_INCREMENT
      , NAME VARCHAR(255)
      , CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP
      , UPDATED_AT DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
      , DELETED_AT DATETIME DEFAULT NULL
      , CONSTRAINT uk_order_name UNIQUE(NAME)
    );
    

    Thực thi migration

    Để tiến hành migrate database schema chúng ta thực thi lệnh ./mvnw flyway:migrate.

    ➜  demo git:(main) ✗ ./mvnw flyway:migrate
    [INFO] Scanning for projects...
    [INFO] 
    [INFO] ------------------------------< app:demo >------------------------------
    [INFO] Building demo 0.0.1-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    [WARNING] The artifact mysql:mysql-connector-java:jar:8.0.31 has been relocated to com.mysql:mysql-connector-j:jar:8.0.31: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
    [INFO] 
    [INFO] --- flyway-maven-plugin:8.5.13:migrate (default-cli) @ demo ---
    [INFO] Flyway Community Edition 8.5.13 by Redgate
    [INFO] See what's new here: https://flywaydb.org/documentation/learnmore/releaseNotes#8.5.13
    [INFO] 
    [INFO] Database: jdbc:mysql://localhost:3306/demo (MySQL 8.0)
    [INFO] Successfully validated 1 migration (execution time 00:00.013s)
    [INFO] Creating Schema History table `demo`.`flyway_schema_history` ...
    [INFO] Current version of schema `demo`: << Empty Schema >>
    [INFO] Migrating schema `demo` to version "20221124103000 - Initial"
    [WARNING] DB: Unknown table 'demo.product' (SQL State: 42S02 - Error Code: 1051)
    [WARNING] DB: Unknown table 'demo.order' (SQL State: 42S02 - Error Code: 1051)
    [INFO] Successfully applied 1 migration to schema `demo`, now at version v20221124103000 (execution time 00:00.100s)
    [INFO] Flyway Community Edition 8.5.13 by Redgate
    [INFO] See what's new here: https://flywaydb.org/documentation/learnmore/releaseNotes#8.5.13
    [INFO] 
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  1.044 s
    [INFO] Finished at: 2023-01-30T14:41:40+07:00
    [INFO] ------------------------------------------------------------------------
    

    Trong trường hợp thực thi migrate có lỗi. Chúng ta có thể thực hiện lệnh ./mvnw flyway:repair trước khi thực thi lại lệnh migrate.

    ➜  demo git:(main) ✗ ./mvnw flyway:repair
    [INFO] Scanning for projects...
    [INFO] 
    [INFO] ------------------------------< app:demo >------------------------------
    [INFO] Building demo 0.0.1-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    [WARNING] The artifact mysql:mysql-connector-java:jar:8.0.31 has been relocated to com.mysql:mysql-connector-j:jar:8.0.31: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
    [INFO] 
    [INFO] --- flyway-maven-plugin:8.5.13:repair (default-cli) @ demo ---
    [INFO] Flyway Community Edition 8.5.13 by Redgate
    [INFO] See what's new here: https://flywaydb.org/documentation/learnmore/releaseNotes#8.5.13
    [INFO] 
    [INFO] Database: jdbc:mysql://localhost:3306/demo (MySQL 8.0)
    [INFO] Repair of failed migration in Schema History table `demo`.`flyway_schema_history` not necessary. No failed migration detected.
    [INFO] Successfully repaired schema history table `demo`.`flyway_schema_history` (execution time 00:00.030s).
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  0.908 s
    [INFO] Finished at: 2023-01-30T14:44:31+07:00
    [INFO] ------------------------------------------------------------------------
    

    Để tiến thành clean database schema chúng ta thực thi lệnh ./mvnw flyway:clean.

    ➜  demo git:(main) ✗ ./mvnw flyway:clean
    [INFO] Scanning for projects...
    [INFO] 
    [INFO] ------------------------------< app:demo >------------------------------
    [INFO] Building demo 0.0.1-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    [WARNING] The artifact mysql:mysql-connector-java:jar:8.0.31 has been relocated to com.mysql:mysql-connector-j:jar:8.0.31: MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.
    [INFO] 
    [INFO] --- flyway-maven-plugin:8.5.13:clean (default-cli) @ demo ---
    [INFO] Flyway Community Edition 8.5.13 by Redgate
    [INFO] See what's new here: https://flywaydb.org/documentation/learnmore/releaseNotes#8.5.13
    [INFO] 
    [INFO] Database: jdbc:mysql://localhost:3306/demo (MySQL 8.0)
    [INFO] Successfully dropped pre-schema database level objects (execution time 00:00.002s)
    [INFO] Successfully cleaned schema `demo` (execution time 00:00.009s)
    [INFO] Successfully cleaned schema `demo` (execution time 00:00.008s)
    [INFO] Successfully dropped post-schema database level objects (execution time 00:00.002s)
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  0.861 s
    [INFO] Finished at: 2023-01-30T14:39:23+07:00
    [INFO] ------------------------------------------------------------------------
    

    Tổng kết

    Trong bài viết này chúng ta đã tìm hiểu cách tạo một instance MySQL sử dụng docker và tạo và quản lý các phiên bản database schema sử dụng Flyway.

  • 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.

  • OWASP Top 10 for Mobile

    OWASP Top 10 for Mobile

    OWASP (Open Web Application Security Project) là dự án xây dựng một nền tảng hướng đến việc tăng cường bảo mật cho phần mềm. Bắt nguồn là cho các dự án Web app, Back end. Tuy nhiên hiện nay thì bảo mật cho các dự án Mobile cũng đã trở nên cấp thiết hơn rất nhiều, và series bài viết này sẽ hướng đến tìm hiểu về các vấn đề về security, phương thức tấn công, các phương pháp phòng tránh, các checklist để tạo ra một project mobile an toàn hơn.

    OWASP Top 10 for Mobile

    1. Improper Platform Usage

    Improper Platform Usage tức là sử dụng các chức năng (features) của nền tảng mobile (iOS/Android) một cách không phù hợp.

    Cụ thể, từng OS system sẽ cung cấp cho developers các tính năng (capabilities, features) có thể sử dụng để phát triển ứng dụng. Nếu developers không sử dụng các tính năng này để phát triển, hoặc sử dụng chúng một cách không chính xác, thì sẽ được gọi là improper use (sử dụng không đúng cách).

    Ví dụ: với iOS thì lưu các thông tin quan trọng của user như passworld hay token thì không được sủ dựng UserDefault để lưu mà cần lưu bằng Keychain, tuy nhiên, việc sử dụng sai config của Keychain cũng tiềm ẩn rủi ro bị tấn công lộ mật khẩu. Hoặc với những quyền truy cập vào thông tin user như Location, HealthKit, Photos cũng không được sử dụng một cách thiếu cân nhắc.

    2. Insecure Data Storage

    Insecure data storage, lưu trữ dữ liệu một cách không an toàn, không đơn thuần chỉ là cách lưu trữ data (ảnh, text, sql data…) một cách không an toàn. Mà còn có thể là log file, cookie file, cached file (URL, browser, third party data…). Một ví dụ điển hình là các developer rất hay sử dụng log trong quá trình phát triển phần mềm, rất dễ tiềm ẩn nguy cơ "lỡ tay" log ra các thông tin nhạy cảm mà quên không loại bỏ, dẫn đến việc bị lọt ra môi trường product.

    Những item cần cân nhắc khi nghĩ đến nguy cơ lộ data:

    • Cách OS cache data, cache image, có thể là cả logging, buffer, thậm chí key-press (đặc biệt là OS mở như Android)
    • Cách các framework được sử dụng trong dự án cache data
    • Cách các thư viện open source cache data, gửi / nhận data

    3. Insecure Communication

    Các ứng dụng mobile thì luôn cần phải transfer data, có thể là giữa client – server, giữa các devices với nhau. Và trong quá trình gửi nhận data, nếu không tuân thủ các quy tắc bảo mật thì có thể bị kẻ xấu tấn công ăn cắp các thông tin nhạy cảm. Đó có thể là password, thông tin account, hoặc các thông tin private của user.

    Việc tấn công ăn cắp thông tin có thể thông qua wifi mà devices đang truy cập, các router nhà mạng hoặc các thiết bị ngoại vi BLE, NFC…

    4. Insecure Authentication

    Insecure Authentication mô tả việc thực hiện xác thực user kém bảo mật dẫn đến người khác có thể lợi dụng để tấn công vào backend nhằm ăn cắp thông tin hoặc thực hiện các request tổn hại đến hệ thống. Vấn đề này có thể do việc thiết kế backend thiếu security như việc request không có access token, hoặc từ bản thân mobile app đã thực hiện việc authenticate offline sơ sài (ví dụ như set passcode ngắn để truy cập vào chức năng quan trọng, hoặc lưu password của user) dẫn đến việc kẻ tấn công có thể ăn cắp thông tin và truy cập vào server.

    5. Insufficient Cryptography

    Việc bảo mật dữ liệu trong mobile app thường áp dụng mã hóa. Tuy nhiên, trong một vài trường hợp thì mã hõa vẫn sẽ tiềm ân rủi ro bị tấn công ăn cắp dữ liệu. Một vài khả năng có thể kể đến như:

    • Quá ỷ lại vào mã hóa của OS (Built-In Code Encryption Processes), ví dụ iOS bản thân nó cũng sẽ mã hóa ứng dụng, tuy nhiên nếu devices bị jail break thì cũng có khả năng decode ứng dụng để lấy dữ liệu.
    • Sử dụng một cơ chế mã hóa đã lỗi thời hoặc tự viết lại thuật toán mã hóa.
    • Lưu trữ encrytion key một cách thiếu bảo mật.

    6. Insecure Authorization

    • Authentication: xác thực user
    • Authorization: xác thực quyền của user

    Việc thiếu sót trong việc xác thực quyền hạn của user trong hệ thống có thể dẫn đến sai sót trong việc cho phép user được truy cập vào những tài nguyên không được cho phép, hoặc có tính bảo mật cao. Dẫn đến việc mất mát các thông tin nhạy cảm của server hoặc bị thực thi những quyền có tính chất ảnh hưởng lớn đến hệ thống. Các vấn đề có thể xảy ra trong trường hợp này như:

    • Server không check quyền của user khi request lên, mà hoàn toàn dựa vào request của client, ví dụ người tấn công có thể lợi dụng bằng cách thêm các param vào GET/POST request để lừa server rằng "tao là admin" và có thể truy cập vào các thông tin nhạy cảm.
    • Một số trường hợp có thể server cung cấp các API dành riêng cho "admin" và nghĩ rằng các user bình thường sẽ không biết các API này nên không cần phải check quyền, tuy nhiên việc này không hề đảm bảo đến việc sẽ bảo mật được các API này mà không lộ ra ngoài cho các user không có quyền khác.

    7. Poor Code Quality

    Poor code quality là các lỗi bảo mật liên quan đến bản thân ngôn ngữ lập trình, có thể kể đến như lỗi:

    • Buffer overflows: các lỗi buffer overflows thì hay xảy ra với các thư viện viết bởi C, C++, và iOS cùng Android đề sử dụng các thư viện C, C++ trong hệ thống, do đó kẻ xấu có thể tấn công vào các thư viện này và qua đó thực thi các đoạn lệnh hoặc thậm chí cài mã độc vào ứng dụng.
    • Format string vulnerabilities: những lỗi dạng này thường là kẻ tấn công cố tình input các đoạn string mã hóa hoặc format đặc biệt vào ứng dụng, hoặc các câu lệnh để tấn công vào ứng dụng. Qua đó có thể gây ra các lỗi như crash ứng dụng, view data trong stack, view thông tin trong memory, thậm chí thực thi source code
    • Tấn công vào third party hoặc tấn công thông qua webview của OS: sử dụng các thư viện kém bảo mật cũng tiềm ẩn nguy cơ bị tấn công. Ngoài ra, trong các version OS cũ, cả iOS và Android đều bộc lộ rất nhiều lỗi liên quan đến webview như thực thi các đoạn javascript nhằm ăn cắp thông tin user.

    8: Code Tampering

    Các ứng dụng mobile, về cơ bản là sẽ nằm trên máy của user, do đó nên ứng dụng mobile rất dễ bị tấn công thay đổi nội dung source code để thực hiện các mục đích xấu. Có thể kể đến các kịch bản tấn công như: kẻ xấu sẽ cài đặt ứng dụng của bạn lên device đã bị jail, qua đó có thể xem được nội dung bên trong của ứng dụng như assets, resource. Thậm chí thay đổi source code hoặc các API được call. Mục đích của việc tấn công này có thể là unlock các chức năng ẩn, hoặc trả phí, hoặc ăn cắp thông tin. Một vài trường hợp có thể là cài mã độc vào, thay đổi asset, resource, sau đó lại distribute ứng dụng lên các kho ứng dụng lậu nhằm ăn cắp thông tin của người tải về. Trước đây thì mình hay jail device để cheat game, hoặc các ứng dụng ngân hàng cũng có thể là mục tiêu yêu thích để bị tấn công dạng này. -> Hãy check Jail/Root khi khởi động ứng dụng.

    9. Reverse Engineering

    Kẻ tấn công sẽ down ứng dụng và sử dụng các tool để tấn con giải mã source code nhằm các mục đích như ăn cắp thông tin trong string table/plist, đọc source code, tìm hiểu thuật toán hoặc các thư viện mà app sử dụng. Với kiểu tấn công này thì mục tiêu tấn công thường là

    • Ăn cắp thông tin về backend server, như url, path, cert nếu có
    • Ăn cắp các key của thuật toán mã hóa
    • Ăn cắp các thông tin liên quan đến sở hữu trí tuệ

    10. Extraneous Functionality

    Các chức năng không liên quan đến ứng dụng?

    Khi ứng dụng được phát triển, trên thực tế nó có rất nhiều các chức năng không thuộc functional requirement và non-functional requirement, nhưng là không thể thiếu trong quá trình phát triển ứng dụng như:

    • Log file, log console để debug những thông tin API, thông tin user hoặc thông tin của ứng dụng để debugging trong quá trình phát triển
    • Các switch flag để on off các chức năng nào đó trong ứng dụng, có thể là chức năng của admin hoặc chức năng liên quan đến A-B testing…
    • Các API path nhằm debug nhưng quên không loại bỏ khi lên môi trường production.

    Các chức năng này nếu bị khai thác có thể làm lộ thông tin về ứng dụng cũng như thông tin về user.

  • Á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.

  • Code sướng tay: Bước 2

    Code sướng tay: Bước 2

    Hi, hôm nay mình lại tiếp tục xàm xí đâyyy. Hôm nay sẽ lại tiếp tục về chuyện đặt tên và thêm về cách viết hàm. Hãy bắt đầu với 1 câu nói sến súa nào :3

    Programs must be written for people to read, and only incidentally for machines to execute.

    (Đại khái là code viết cho người đọc, chỉ là vô tình máy chạy được thôi)

    Abelson and Sussman

    Và để người đọc được thì sao không viết code một cách có-thể-đọc-được nhỉ?

    // Kiểu như này
    // "If errors is empty..."
    if (errors.isEmpty) ...
    
    // "Hey, subscription, cancel!"
    subscription.cancel();
    
    // "Get the monsters where the monster has claws."
    monsters.where((monster) => monster.hasClaws);
    // Thay vì như này
    // Telling errors to empty itself, or asking if it is?
    if (errors.empty) ...
    
    // Toggle what? To what?
    subscription.toggle();
    
    // Filter the monsters with claws *out* or include *only* those?
    monsters.filter((monster) => monster.hasClaws);

    Tuy nhiên đừng cố gắng để nó quá dễ đọc đến mức như đang nói, điều này làm code rườm rà và trở nên khó đọc hơn bao giờ hết!

    // has gone too far
    if (theCollectionOfErrors.isEmpty) ...
    
    monsters.producesANewSequenceWhereEach((monster) => monster.hasClaws);

    Cách đặt tên cho câu hỏi yes/no

    Tên trả lời cho câu hỏi yes/no thường được dùng làm các điều kiện rẽ nhánh, so sánh 2 cách viết sau:

    if (window.closeable) ...  // Adjective.
    if (window.canClose) ...   // Verb.

    Chúng ta có thể đặt tên như sau:

    • Bắt đầu với “to be”isEnabledwasShownwillFire. Đây là cách phổ biến nhất (window.closeable cũng có thể được đặt là window.isCloseable)
    • Sử dụng trợ động từhasElementscanCloseshouldConsumemustSave.
    • Các động từ thông thường không hay được dùng trong TH này

    Chúng ta có thể đặt tên bắt đầu với những động từ này mà không bị nhầm lẫn với các hàm do nó không phải là các động từ mệnh lệnh.

    // Nên đặt như này
    // Tuy nhiên, trong một số trường hợp
    // bỏ qua các tiền tố yes/ no này mà nghĩa vẫn rõ ràng thì nên bỏ đi
    isEmpty
    hasElements
    canClose
    closesWindow
    canShowPopup
    hasShownPopup
    // Chứ không phải là như này
    empty         // Adjective or verb?
    withElements  // Sounds like it might hold elements.
    closeable     // Sounds like an interface.
                  // "canClose" reads better as a sentence.
    closingWindow // Returns a bool or a window?
    showPopup     // Sounds like it shows the popup.

    Tiếp theo là về việc lùi dòng và độ phức tạp của code trong phạm vi hàm. Mỗi lần bạn lùi vào 1 tab, vậy là code của bạn đã phức tạp và tốn công sức để đọc thêm 1 bậc. Mình rất thích đọc và xử lí hết lỗi trong tab Problems này cho đến khi nó hiện lên dòng chữ như vầy:

    Không còn lỗi nữa~

    Và khi mình code js, đã có rất nhiều cảnh báo bắn ra pằng pằng khi mình code của mình quá dài, nó sẽ yêu cầu mình tách hàm ra (tính ra IDE giúp được rất nhiều, nó còn có thể detect các đoạn code giống nhau mà mình copy paste sau đó bảo mình tách hàm đi bạn ơi~). IDE sẽ cảnh báo khi bạn lùi vào quá nhiều và yêu cầu bạn refactor code. IDE làm vậy không phải vì nó không đọc được code của bạn, mà vì nó sợ sau này bạn sẽ không đọc được code của bạn đấy :P. Vậy tại sao trong 1 hàm lại cần tab vào nhiều đến thế? Đó là vì hàm đó đang cố gắng làm quá nhiều việc.

    Có một nguyên lí rất nổi tiếng trong giới lập trình viên, gọi là Single Responsibility Principle (nguyên lí đơn nhiệm). Nguyên lí này khi áp dụng cho một hàm thì có nghĩa là: 1 hàm chỉ nên làm 1 việc. Đôi khi 1 việc này sẽ hơi tốn code, nhưng trong hầu hết các trường hợp (nếu không phải bạn đang thực thi một thuật toán gì đó phức tạp) thì nó sẽ có thể viết trong chiều dài màn hình của bạn, và không quá 3 lần tab vào (theo ý kiến của mình, hoặc số lần tab vào này có thể phụ thuộc vào trí nhớ của bạn nhưng với mình 4 là quá nhiều để nhớ, nếu mỗi lần tab vào đi kèm 1 điều kiện thì đã có 16 trường hợp nhỏ nhất đang chờ được mình duyệt qua T_T). Có 1 tip nhỏ để giảm bớt độ phức tạp lùi dòng, đó là sử dụng early return (1 lần duy nhất) trong hàm của bạn. Điều này sẽ giảm tab complexity xuống 1 level rồi đó :3

    Early Return (thoát sớm)

    Tuy nhiên, không phải lúc nào chúng ta cũng nên chia ra quá nhiều hàm với 1 đống param đi kèm, điều này sẽ tạo ra rất nhiều các hàm ăn bám (hàm chỉ dùng được duy nhất 1 lần cho 1 hàm khác) và thừa thãi. Hãy tách hàm sao cho hợp lí để dễ dàng đọc (và đôi khi là dùng) chúng nhé :3

    Chúc mọi người sẽ code sướng cái tay và đọc code sướng con mắt hehe. À với Tết rồi nên chúc ai đọc bài viết này sẽ đẹp trai/xinh gái, nhiều tiền, nhiều sức khoẻ, vui vẻ, hạnh phúc và không bị đồng nghiệp (hoặc sếp) chửi vì code lởm ạ.

    Tham khảo:

    https://dart.dev/guides/language/effective-dart/design

    https://stackoverflow.com/questions/475675/when-is-a-function-too-long

  • 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
  • Nghiên cứu

    Nghiên cứu

    Nghiên cứu

    Thi thoảng mình nhận được một số câu hỏi của bạn bè, trong đó có câu: Mày làm Nghiên cứu hay Ứng dụng ??

    Mình trộm nghĩ, nếu nói tao làm nghiên cứu thì chỉ đúng một phần mà nói tao làm ứng dụng thì cũng chỉ đúng được một phần. Nên tấu hài rằng: “Tao làm nghiên cứu ứng dụng”. Kể ra cũng… hợp lý ?

    Mình luôn tự coi bản thân là (hoặc cố gắng để trở thành) một Kỹ sư phần mềm, bấy lâu nay mình vẫn luôn nghĩ vậy. Và khi gặp một vấn đề kỹ thuật khó, thì một Kỹ sư bắt buộc (hoặc cần) phải có năng lực nghiên cứu giải pháp. Lấy ví dụ, mình đang cần phát triển và triển khai 1 mô hình Machine Learning (nhẹ, không cần GPU) và 1 mô hình Deep Learning (nặng, có thể cần GPU) trên môi trường AWS; yêu cầu Kỹ thuật đặt ra là tối thiểu việc vận hành hạ tầng cũng như việc Update mô hình khi cần thiết –> có rất nhiều solutions khác nhau –> cần Re-search. Một vài phân tích đơn giản như; do business mà các hệ thống này ít được sử dụng thường xuyên –> chúng ta nên dùng Serverless để tối ưu chi phí (hoặc Spot instance); với hệ thống Machine Learning, do đặc điểm nhẹ, suy diễn nhanh –> có thể sử dụng AWS Lambda –> đến đây cần nghiên cứu cách sử dụng Aws Lambda sao cho hiệu quả (bởi một số giới hạn Package size) –> cần thực nghiệm các giải pháp khác nhau: Lambda layers, Elastic File System, Docker Image,… Một ví dụ khác là thằng bạn mình loay hoay tìm cách Crawl hết sản phẩm của các trang thương mại điện tử, rồi tìm cách lưu trữ và sử dụng sao cho hiệu quả,… cũng phải mất khá nhiều thì giờ để Re-search (search đi search lại).

    Tóm lại, mình nghĩ đơn giản Nghiên cứu (hay Research) tức là “search đi search lại”. Không cứ phải phát minh ra một cái vĩ đại, chưa ai làm mới là Nghiên cứu, mà đơn giản chỉ cần giải quyết đúng bài toán mà mình gặp phải… đó cũng có thể gọi là nghiên cứu được chứ nhỉ ? Nghiên cứu thường đi kèm với năng lực phân tích vấn đề, bởi chỉ có phân tích thì chúng ta mới chỉ ra được ưu nhược điểm của giải pháp, tại sao nó lại phù hợp với bài toán của mình. Chính nhờ sự nghiên cứu mà năng lực, kỹ năng của Kỹ sư tăng lên –> qua đó sẽ có khả năng phán đoán, quyết định giải pháp tốt hơn, nhạy bén hơn –> Thu nhập cao hơn ?

    Technologies come and technologies go, but insight is forever.

  • [MyBatis] Sử dụng MyBatis với Spring Boot

    [MyBatis] Sử dụng MyBatis với Spring Boot

    MyBatis là gì?

    MyBatis là một framework nổi tiếng trong cộng đồng Java. Nó là triển khai của tầng lưu trữ trong kiến trúc phân tầng trên nền tảng Java tương tự như Hibernate hoặc ngay cả là JDBC thuần. Nó giúp việc triển khai tầng lưu trữ trở nên đơn giản hơn với nhà phát triển. Thông tin về MyBatis bạn có thể tham khảo tại đây. Nội dung bài viết này tập trung vào cách sử dụng MyBatis cùng với Spring Boot.

    MyBatis-Spring-Boot-Starter

    Để sử dụng MyBatis với Spring Boot chúng ta sử dụng thư viện MyBatis-Spring-Boot-Starter. Đây là thư viện hỗ trợ cấu hình nhanh chóng một ứng dụng Spring Boot có sử dụng MyBatis. Thư viện này được hỗ trợ chính thức từ nhóm phát triển MyBatis. Các bước cấu hình tại đây.

    Repository hay Mapper

    Mapper là một Java interface mà các phương thức được ánh xạ tới các truy vấn SQL tương ứng. Mặc định MyBatis sẽ ánh xạ các method được định nghĩa trong các interface được đánh dấu với annotation @Mapper tới các truy vấn SQL tương ứng.

    ProductRepository

    Dưới đây là một ví dụ về Mapper. ProductRepository được định nghĩa trong package app.demo.mybatis.repository

    
    @Mapper
    public interface ProductRepository {
    
      void create(Product product);
    }
    

    Để định nghĩa truy vấn SQL tương ứng chúng ta tạo ProductRepository.xml trong thư mục app.demo.mybatis.repository bên trong resources.

    
    
    
    
      
        
      
    
    

    Trong ví dụ trên chúng ta đang ánh xạ phương thức create với câu truy vấn INSERT. Khi phương thức create được gọi thì câu truy vấn sẽ được thực thi. Kết quả câu truy vấn sẽ được trả về phương thức create.

    DataSource

    Trong bài viết này chúng ta sẽ sử dụng MySQL để thực hành với MyBatis. Chúng ta có thể sử dụng docker để tạo container chạy MySQL với câu lệnh sau:

    docker run --name demo -e MYSQL_ROOT_PASSWORD=demo@123 -p 3306:3306 -d mysql --lower_case_table_names=1
    

    Với câu lệnh trên chúng ta đã tạo xong MySQL với schema demo cũng như mật khẩu cho tài khoản rootdemo@123.

    Để cấu hình data source với Spring Boot chúng ta thêm các cấu hình sau trong application.yml:

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/demo?createDatabaseIfNotExist=true
        username: root
        password: demo@123
        driverClassName: com.mysql.cj.jdbc.Driver
    

    BindingException

    org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
    

    Lỗi này xuất hiện khi chưa cấu hình thuộc tính mybatis.mapper-locations. Cấu hình thuộc tính này trong application.yml hoặc application.properties như sau:

    mybatis:
      mapper-locations: "classpath:app.demo.mybatis.repository/*.xml"
    
    mybatis.mapper-locations=classpath:app.demo.mybatis.repository/*.xml
    

    Tổng kết

    Trong bài viết này tôi đã hướng dẫn các bạn các bước cơ bản để tạo một ứng dụng Spring Boot có sử dụng MyBatis. Hi vọng bài viết sẽ giúp ích cho các bạn mới bắt đầu tiếp cận với MyBatis cũng như là cung cấp một hướng dẫn khi bắt đầu xây dựng một dự án.

  • Spring Security: Tìm hiểu về internal flow

    Spring Security: Tìm hiểu về internal flow

    Spring Security là gì?

    Spring Security là một framework được cung cấp bởi Spring cung cấp khả năng xác thực, bảo vệ, kiểm soát truy cập và có khả năng tuỳ biến cao. Tập trung chủ yếu vào Authentication và Authorization cho một ứng dụng Java.

    Giống như hầu hết các Spring projects khác, sức mạnh thực sự của Spring Security đến từ việc nó có thể dễ dàng mở rộng khi cần thiết với những yêu cầu cụ thể trong một dự án.

    Benefits/features chính:

    • Hỗ trợ authentication và authorization một cách toàn diện.
    • Ngăn chặn các nguy cơ bảo mật đến từ Cross-site Forgery, CSRF Attacks, ClickJacking,…
    • Hỗ trợ tích hợp với Spring Web MVC
    • Hỗ trợ tích hợp với Servlet API

    Internal Workflow: Cách Spring Security hoạt động?

    Dưới đây là workflow cách mà Spring Security mặc định (User Credentials) hoạt động:

    User Credentials Authentication workflow

    Ta hãy cùng đến với những objects chính có trong flow và tìm hiểu định nghĩa của chúng nhé.

    Spring Security Filters:

    Spring Security Authentication Filters là những filter sẽ nằm giữa client request với server. Khi nhận được request, các filter sẽ tách lọc những thông tin từ request thành các authentication details (username, password, roles,…). Default Spring Security sẽ sử dụng class UsernamePasswordAuthenticationFilter.

    UsernamePasswordAuthenticationFilter extends từ Abstract class AbstractAuthenticationProcessingFilter.

    Authentication: là một base object làm nhiệm vụ validate user credentials nhận được từ phía client. Ở behavior mặc định, Authentication object sẽ là class UsernamePasswordAuthenticationToken.

    UsernamePasswordAuthenticationToken sẽ được sử dụng để chứa user credentials.

    AuthenticationManager:

    AuthenticationManager là một interface với method authenticate() làm nhiệm vụ xác định những Authentication providers phù hợp nhất để xử lý Authentication object nhận được từ filters. AuthenticationManager sẽ nhận kết quả authenticate từ Provider (Success hoặc Not success). Nếu không success, nó sẽ thử một provider phù hợp khác.

    Ở behavior mặc định của Spring security, class ProviderManager sẽ được chọn để xử lý các request.

    ProviderManager implements interface AuthenticationManager.

    AuthenticationProvider:

    AuthenticationProvider là những classes implement interface AuthenticationProvider với method authenticate() làm nhiệm vụ xử lý các logic liên quan đến authentication. DaoAuthenticationProvider sẽ là authentication provider mặc định cho behavior mặc định của Spring Security.

    DaoAuthenticationProvider.

    UserDetailsService: là interface chứa thông tin, schema của user details. Ở behavior mặc định, Spring Security sẽ sử dụng class InMemoryUserDetailsManager, với method loadUserByUsername() để lấy ra thông tin của user từ memory của hệ thống.

    PasswordEncoder: là interface có nhiệm vụ encode, encrypt và decrypt password của user, validate và trả về kết quả valid/invalid cho Authentication Provider xử lý.

    Security Context:

    Sau khi Spring Security đã validate đủ, user details sẽ được lưu vào Security context. Ở lần truy cập tới, thông tin user sẽ được filter retrieve ở đây thay vì thực hiện đầy đủ các bước flow như ở trên.

    Kết luận

    Trên đây là tổng hợp về Spring Security cũng như một flow mặc định của Spring Security sẽ diễn ra như thế nào. Tất nhiên, còn rất nhiều những vấn đề to lớn khác từ Spring Security mà phạm vi bài viết không thể mô tả đủ. Hi vọng, qua bài viết trên, bạn đã có thể có cái nhìn tổng quan nhất về Spring Security, rất cảm ơn bạn đã dành thời gian ra để đọc qua bài viết trên của mình.

    Tài liệu tham khảo

    https://spring.io/projects/spring-security

    https://blog.knoldus.com/spring-security-internal/

    https://www.linkedin.com/pulse/how-does-spring-security-works-internally-ayush-jain/