Blog

  • Triển khai Continuous Delivery cho dự án Serverless Backend với Gitlab-CI và AWS Lambda Function

    Triển khai Continuous Delivery cho dự án Serverless Backend với Gitlab-CI và AWS Lambda Function

    Article overview

    Giả sử chúng ta phát triển một sản phẩm Serverless Backend với AWS Lambda Function và mong muốn áp dụng CD để tự động hoá công việc deploy lên Cloud.
    Bài viết áp dụng cho cấu trúc hệ thống git với mỗi một Function sẽ có một branch phát triển riêng. Ví dụ source code cho Function authentication sẽ được lưu ở branch master-authentication.

    Tổng quan về các công nghệ sử dụng:

    • NodeJS
    • Gitlab-CI
    • AWS Lambda Function, AWS CLI
    • Môi trường MacOS, Linux

    Table of contents

    Chúng ta cần một số bước sau:

    Cài đặt và cấu hình môi trường tại thiết bị chạy service runner

    Đầu tiên, chúng ta cần cài đặt AWS CLI.
    Sau khi cài đặt xong, ta thực hiện config với thông tin của AWS User có quyền deploy lên AWS S3 với câu lệnh sau:

    $ aws configure
    AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
    AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    Default region name [None]: us-west-2
    Default output format [None]: json

    Cấu hình các job CI/CD với gitlab-ci.xml và Gitlab-CI

    Đầu tiên, để có thể update code lên AWS Lambda chúng ta sẽ sử dụng command aws lambda update-function-code.

    Command hỗ trợ tham số –zip-file để upload source code dưới dạng .zip file, nên việc đầu tiên chúng ta cần làm là zip source code lại.
    zip -r deploy.zip .
    Sau khi zip xong, ta thực hiện deploy zip file lên AWS Lambda bằng câu lệnh sau:

    aws lambda update-function-code --function-name authentication --zip-file fileb://deploy.zip

    Với authentication là tên của Lambda Function, deploy.zip là tên file zip cần đẩy lên.

    Ta sẽ setting command cho package.json như sau:

    "scripts": {
        "deploy": "zip -r deploy.zip . && aws lambda update-function-code --function-name authentication --zip-file fileb://deploy.zip"
    }

    Tiếp đó, ta sẽ cấu hình .gitlab-ci.yml để hệ thống tự động deploy khi có thay đổi trên nhánh master-authentication.

    stages:
      - Deployment
    deploy:
      stage: Deployment
      before_script: []
      only:
          - master-authentication
      allow_failure: true
      script:
        - yarn install --production 
        - yarn deploy

    Sau đó merge code vào master-authentication, và hưởng thành quả. Từ giờ các bạn không cần phải deploy bằng tay nữa rồi, chúc các bạn may mắn.

    Authors

    ThangPV12

  • Coding convention – Những điều cần biết trước khi bắt tay vào code (Part 2)

    Coding convention – Những điều cần biết trước khi bắt tay vào code (Part 2)

    Table of contents

    • Magic number & Duplicate code
    • Code Organization
    • Scene Delegate
    • Computed Property
    • Optional
    • Multi-line String
    • Bonus

    Magic number & Duplicate code

    • Khi code ta không nên dùng những con số vô định, hay còn gọi là magic number, gây khó hiểu cho người khác. Điều này sẽ ảnh hưởng đến quá trình maintain sau này. Ta có thể thay thế những con số magic này bằng cách tạo ra các constant với tên gọi clear nhất có thể, làm sao để khi người khác đọc code của bạn, họ cũng có thể hiểu được vì sao bạn lại dùng đến con số đó. Hoặc ít nhất trước khi dùng magic number, ta phải thêm comment để giải thích lý do tại sao sử dụng chúng.
    • Ta cũng không nên để những đoạn code giống nhau được lặp đi lặp lại trong source code của mình. Nếu nhận thấy có những đoạn code cùng thực hiện một chức năng nhất định, hoặc cùng được apply cho 1 đối tượng nhất định, ta có thể nghĩ đến việc grouping chúng lại thành các function để tiện cho việc implement cũng như maintain sau này.

    Not Preferred

    class SignUpViewController: UIViewController {
    
        @IBOutlet private weak var firstNameTextField: UITextField!
        @IBOutlet private weak var lastNameTextField: UITextField!
        @IBOutlet private weak var accountTextField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Có thể thấy đoạn code này đang set borderWidth, borderColor, cornerRadius cho
            // lần lượt 3 TextField khác nhau. Tưởng tượng nếu sau này màn hình được update
            // thêm nhiều textfield khác nữa, hoặc trong app cũng có nhiều textfield cần phải 
            // setup các thuộc tính tương tự như trên. Mỗi textfield cần 3 dòng code x số lượng
            // textfield cả app = ...
            // -> Ta nghĩ đến việc tạo ra các function common để dùng chung cho các đối tượng 
            // UITextField, sẽ thuận tiện hơn cho việc implement và maintain sau này, sửa 1 hàm 
            // có thể apply được toàn bộ
            firstNameTextField.layer.borderColor = UIColor.orange.cgColor
            firstNameTextField.layer.borderWidth = 1.0
            firstNameTextField.layer.cornerRadius = 25
            
            lastNameTextField.layer.borderColor = UIColor.orange.cgColor
            lastNameTextField.layer.borderWidth = 1.0
            lastNameTextField.layer.cornerRadius = 25
            
            accountTextField.layer.borderColor = UIColor.orange.cgColor
            accountTextField.layer.borderWidth = 1.0
            accountTextField.layer.cornerRadius = 25
        }
    }
    

    Preferred

    class SignUpViewController: UIViewController {
    
       @IBOutlet private weak var firstNameTextField: UITextField!
       @IBOutlet private weak var lastNameTextField: UITextField!
       @IBOutlet  weak var accountTextField: UITextField!
       
       override func viewDidLoad() {
           super.viewDidLoad()
           
           firstNameTextField.setupLayer()
           lastNameTextField.setupLayer()
           accountTextField.setupLayer()
       }
    }
    // Phần extension cho các compoment như UILabel, UITextField, UIButton, ... thường 
    // được tách ra thành các file riêng. Xem thêm phần Code Organization
    extension UITextField {
       func setupLayer(borderWidth: CGFloat = 1.0,
                       borderColor: CGColor = UIColor.orange.cgColor,
                       cornerRadius: CGFloat = 25) {
           layer.borderWidth = borderWidth
           layer.borderColor = borderColor
           layer.cornerRadius = cornerRadius
       }
    }

    Code Organization

    • Ta không nên gộp tất cả các property và các function vào trong một block code ở trong một file. Vì đối với những file có số lượng dòng code lớn, việc làm này sẽ khiến ta khó hình dung được cấu trúc tổ chức của file và mục đích sử dụng của các function trong file đó. Ta nên chia nhỏ file thành nhiều block code, mỗi block code giải quyết một nhiệm vụ khác nhau và chứa các function liên quan tới nhiệm vụ đó. Ta có thể thêm từ khoá // MARK: – dosomething vào trên mỗi block code

    Not Preferred

    // 1 block code không nên adopt quá nhiều protocol, delegate như vậy
    class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
        @IBOutlet private weak var salaryTableView: UITableView!
        @IBOutlet private weak var avatarImageView: UIImageView!
        @IBOutlet private weak var widthConstraintAvatarImageView: NSLayoutConstraint!
        
        private var dataSalaryArray: [Salary] = [Salary(income: 123456, month: "1/2021"),
                                                Salary(income: 654321, month: "2/2021")]
        
        override func viewDidLoad() {
            super.viewDidLoad()
            salaryTableView.delegate = self
            salaryTableView.dataSource = self
            salaryTableView.register(UINib(nibName: "SalaryCell", bundle: nil), forCellReuseIdentifier: "SalaryCell")
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return dataSalaryArray.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            guard let cell: SalaryCell = salaryTableView.dequeueReusableCell(withIdentifier: "SalaryCell",
                                                                             for: indexPath) as? SalaryCell else {
                return UITableViewCell()
            }
            cell.setupData(data: dataSalaryArray[indexPath.row])
            return cell
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            
        }
    
        func loadData() {
            // Do something
        }
    }
    

    Preferred

    class HomeViewController: UIViewController {
    
       // MARK: - Outlet
       @IBOutlet private weak var salaryTableView: UITableView!
       @IBOutlet private weak var avatarImageView: UIImageView!
       @IBOutlet private weak var widthConstraintAvatarImageView: NSLayoutConstraint!
       
       // MARK: - Property
       private var dataSalaryArray: [Salary] = [Salary(income: 123456, month: "1/2021"),
                                               Salary(income: 654321, month: "2/2021")]
       
       override func viewDidLoad() {
           super.viewDidLoad()
           salaryTableView.delegate = self
           salaryTableView.dataSource = self
           salaryTableView.register(UINib(nibName: "SalaryCell", bundle: nil), forCellReuseIdentifier: "SalaryCell")
       }
    }
    
    // MARK: - TableView DataSource
    extension HomeViewController: UITableViewDataSource {
       func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
           return dataSalaryArray.count
       }
       
       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           guard let cell: SalaryCell = salaryTableView.dequeueReusableCell(withIdentifier: "SalaryCell",
                                                                            for: indexPath) as? SalaryCell else {
               return UITableViewCell()
           }
           cell.setupData(data: dataSalaryArray[indexPath.row])
           return cell
       }
    }
    
    // MARK: - TableView Delegate
    extension HomeViewController: UITableViewDelegate {
       func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
           
       }
    }
    
    // MARK: - Do something
    extension HomeViewController {
       func loadData() {
           // Do something
       }
    }
    
    • Khi thêm từ khoá // MARK: – dosomething vào trước các block code, Xcode sẽ tự gen cho ta đường line phân cách giữa các block đó. Thêm vào đó, khi tap vào thanh công cụ như trên ảnh, ta cũng có thể overview được trong file đang có những block code nào, các block đảm nhận nhiệm vụ gì, các function liên quan đến nhiệm vụ đó. Việc này rất có ích khi file của bạn có số lượng dòng code lớn và có nhiều người cùng phát triển

    • Tương tự, ta cũng nên chia project thành nhiều các folder, mỗi folder có chức năng riêng và chứa các file liên quan đến chức năng đó.
    Preferred Not Preferred

    Scene Delegate

    • Khi tạo một project mới trong Xcode, hệ thống sẽ mặc định cho rằng app của bạn sẽ chỉ support cho version iOS cao nhất mà bản Xcode đó support trở lên (ví dụ: Xcode 11.3 sẽ support cho version iOS 13.6 trở lên, Xcode 12.0 sẽ support cho version iOS 14.0 trở lên, Xcode 12.5 là iOS 14.5 trở lên, …).
    • Từ Xcode 11.0 trở về trước, khi tạo mới 1 project, sẽ có 1 vài file mặc định được tạo như: ViewController.swift, Main.storyboard, AppDelegate.swift, Info.plist… Từ Xcode 11.0 trở lên, để phục vụ cho iOS 13, ngoài những file mặc định vừa liệt kê ở trên, còn có thêm 1 file nữa là SceneDelegate.swift (Tìm hiểu thêm tại đây).

    • Vì vậy, nếu muốn project có thể được build trên device (hoặc simulator) chạy version < iOS 13, ta sẽ phải tiến hành xoá file SceneDelegate.swift và thực hiện 1 vài config như sau.
    • Ngoại trừ trường hợp là bạn muốn làm việc với SwiftUI, việc xoá file SceneDelegate.swift và giảm target version build của app dường như là 1 việc bắt buộc bởi 1 vài lý do chính sau:
      • Khi phát triển 1 ứng dụng, chắc chắn chúng ta đều muốn đông đảo người dùng có thể tiếp cận được với ứng dụng đó. Nhưng không phải người dùng nào cũng sẵn sàng update version iOS mới nhất cho device của họ. Vì vậy việc để target vesion build của app ở mức “phổ thông” như iOS 11.0, 12.0, … sẽ làm tăng tính thương mại cho app của bạn.
      • Về mặt technical, khi build app trên những device “thấp” hoặc trên những iOS version thấp, ta sẽ có cơ hội để test nhiều hơn. Vì thực tế sẽ có rất nhiều bug rất “dị”, chúng chỉ xảy ra trên những “môi trường” thấp mà không xảy ra ở trên “môi trường” cao, hoặc cũng có nhiều trường hợp ngược lại. Đối với 1 developer, ta cần phải đảm bảo những dòng code của ta phải chạy ngon trên nhiều môi trường khác nhau.
      • Cũng về mặt technical, khi bạn muốn chia sẻ source code của mình và người khác muốn clone về. Trong trường hợp bạn dùng bản Xcode mới nhất còn họ dùng bản Xcode thấp hơn, nếu bạn không giảm target version build thì người khác sẽ không thể build source của bạn được.

    Computed Property

    • Để cho ngắn gọn, nếu 1 computed property thuộc kiểu read-only, ta có thể bỏ qua mệnh đề get. Mệnh đề này chỉ cần thêm vào khi có thêm cả mệnh đề set

    Preferred

    var diameter: Double {
      return radius * 2
    }
    

    Not Preferred

    var diameter: Double {
      get {
        return radius * 2
      }
    }
    

    Optional

    • Khi truy cập giá trị optional, nếu giá trị đó chỉ được truy cập 1 lần hoặc có nhiều optional trong chuỗi, ta có thể dùng 1 chuỗi optional liên tiếp
    textContainer?.textLabel?.setNeedsDisplay()
    
    • Trong trường hợp giá trị optional được truy cập nhiều lần, nên sử dụng if…let để mở ra 1 block code rồi thao tác trong đó
    if let textContainer = textContainer {
      // do many things with textContainer
    }
    
    • Khi đặt tên cho các biến và các property optinal, không cần thiết phải đặt kiểu như optionalNameLabel hay couldAvatarImageView vì trạng thái optional (?) đã có trong khi khai báo biến rồi.
    • Khi unwrapp biến optional, cũng không cần thiết phải đặt các tên như unwrappedView hay realLabel mà hãy dùng chính tên gốc của biến đó

    Preferred

    var subview: UIView?
    var volume: Double?
    
    // later on...
    if let subview = subview, let volume = volume {
      // do something with unwrapped subview and volume
    }
    
    // another example
    resource.request().onComplete { [weak self] response in
      guard let self = self else { return }
      let model = self.updateModel(response)
      self.updateUI(model)
    }
    

    Not Preferred

    var optionalSubview: UIView?
    var volume: Double?
    
    if let unwrappedSubview = optionalSubview {
      if let realVolume = volume {
        // do something with unwrappedSubview and realVolume
      }
    }
    
    // another example
    UIView.animate(withDuration: 2.0) { [weak self] in
      guard let strongSelf = self else { return }
      strongSelf.alpha = 1.0
    }
    
    • Nếu có nhiều biến optional được unwrapp với guard let hoặc if…let, hãy ghép chúng lại với nhau thành 1 câu lệnh để giảm thiếu việc lồng điều kiện. Khi ghép, hãy đặt guard trên 1 dòng riêng, đặt các điều kiện trên từng dòng riêng và thụt lề cho chúng, cuối cùng mệnh đề else được căn lề thẳng với guard

    Preferred

    guard 
      let number1 = number1,
      let number2 = number2,
      let number3 = number3 
    else {
      fatalError("impossible")
    }
    // do something with numbers
    

    Not Preferred

    if let number1 = number1 {
      if let number2 = number2 {
        if let number3 = number3 {
          // do something with numbers
        } else {
          fatalError("impossible")
        }
      } else {
        fatalError("impossible")
      }
    } else {
      fatalError("impossible")
    }
    
    
    

    Multi-line String

    • Khi muốn viết 1 văn bản dài nhiều dòng, nên sử dụng cú pháp Multi-line String Literal. Đó là mở văn trên 1 dòng rồi bắt đầu văn bản từ dòng thứ 2 trở đi, kết hợp với việc thụt lè và căn lề thằng các dòng tiêp theo

    Preferred

    let message: String = """
      You cannot charge the flux \
      capacitor with a 9V battery.
      You must use a super-charger \
      which costs 10 credits. You currently \
      have \(credits) credits available.
      """
    

    Not Preferred

    let message: String = """You cannot charge the flux \
      capacitor with a 9V battery.
      You must use a super-charger \
      which costs 10 credits. You currently \
      have \(credits) credits available.
      """
    

    Bonus

    • Không giống như 1 số ngôn ngữ khác (C/C++, JavaScript, …), Swift không yêu cầu phải có dấu chấm phảy ở cuối mỗi dòng code

    Preferred

    let swift = "not a scripting language"
    

    Not Preferred

    let swift = "not a scripting language";
    
    • Dấu ngoặc đơn bao quanh các điều kiện là không bắt buộc và nên được bỏ qua

    Preferred

    if name == "Hello" {
      print("World")
    }
    

    Not Preferred

    if (name == "Hello") {
      print("World")
    }
    
    • Tuy nhiên với những biểu thức phức tạp, có dấu ngoặc đơn bao quanh sẽ làm code trở nên clear hơn

    Preferred

    let playerMark: String = (player == current) ? "X" : "O"

    • Ngoài ra, còn 1 lỗi nữa mà người viết thấy đa phần các bạn newbie rất hay mắc phải. Tuy rằng lỗi này liên quan đến logic nhưng cũng xin liệt kê vào đây để anh em newbie tiện theo dõi. Đó là khi ta muốn truy xuất các phần tử trong 1 collection data (set, dictionary, array, … và ở đây xin lấy ví dụ là array), việc đầu tiên ta cần làm là phải check xem array đó có phần tử hay không rồi mới tiến hành việc truy xuất. Dưới đây là 1 ví dụ:

    Preferred

    private func getDataFromPlistFile(_ name: String) -> Dictionary<String, AnyObject>? {
    if PlistManager().getPlist(withName: name)?.count == 0 {
    return nil
    }
    let data = PlistManager().getPlist(withName: name)?[0]
    return data
    }

    Chúng ta có thể viết clear hơn như sau (dĩ nhiên đây là cách được recommend):

    Preferred

    private func getDataFromPlistFile(_ name: String) -> Dictionary<String, AnyObject>? {
    if let data = PlistManager().getPlist(withName: name)?.first {
    return data
    }
    }

    Tuyệt đối không được truy xuất trực tiếp vào các phần tử của array khi chưa check điều kiện, vì ta không thể chắc chắn được là array đó luôn có phần tử hay là không. Điều này sẽ tiềm ẩn nguy cơ bug rất cao.

    Not Preferred

    private func getDataFromPlistFile(_ name: String) -> Dictionary<String, AnyObject>? {
    let data = PlistManager().getPlist(withName: name)?[0]
    return data
    }

    Reference

  • Giao thức bảo mật HTTPS và MITM attack(Secure Coding P1)

    Giao thức bảo mật HTTPS và MITM attack(Secure Coding P1)

    Giao thức bảo mật HTTPS và MITM attack(Secure Coding P1)

    Table of contents

    Giao thức bảo mật HTTPS và MITM attack

    Như tất cả lập trình viên đều biết: HTTPS là một giao giức bảo mật, dữ liệu được mã hóa trên đường truyền, các bước bắt tay (handshake) để mã hóa được dữ liệu của nó tóm gọn bởi các bước bên dưới

    Https handshake

    Các bước handshake này sẽ đảm bảo dữ liệu giữa client và server được mã hóa bởi một key mà chỉ có client và server biết. Sẽ không ai có thể đọc trộm hoặc sửa đổi các gói tin giữa client và server

    Tuy nhiên, nhìn sơ đồ trên chúng ta có thể thấy mắt xích yếu nhất của các bước HandShake chính là step 1 và step 2

    Về mặt logic: Nếu như ở Step 1 client say hello với "ai đó" ko phải server, Step 2 server response với "ai đó" ko phải là client thì sao? nếu như client và sever ko làm làm việc trực tiếp với nhau mà thông qua "ai đó" thì Masterkey ở step 7 đã bị "ai đó" lấy – và "ai đó" có khả năng tóm được gói tin, có khả năng giải mã được gói tin, có khả năng gói tin sẽ bị sửa đổi trong quá trình khi gửi nhận giữa client và server?

    Nếu điều này xảy ra, đó chính là MTTM attack – Man In The Middle Attack – một hình thức tấn công chen vào giữa đường truyền để lấy dữ liệu, sửa đổi, giả mạo gói tin

    Hầu hết các kết nối hiện tại của web, mobile đều đang sử dụng HTTPS để gửi nhận dữ liệu trên đường truyền. Điều này khiến đại đa số lập trình viên yên tâm và hài lòng với về mức độ an toàn này. Nếu sử dụng cách chặn gói tin thông thường thì cái mà attacker thu được chỉ là một gói dữ liệu đã mã hóa – không có giá trị gì.

    Nhưng thực tế có đúng như thế ko?

    Không, tất nhiên là không, hiện thực tàn khốc hơn thế rất nhiều

    Trên thực tế, dữ liệu gửi từ A sang B trên Internet ko thực sự đi trực tiếp từ A sang B, mà có thể nó sẽ còn phải qua rất nhiều server Trung gian khác.

    Quay lại lý thuyết về các bước HandShake – với hai mắt xích yếu nhất là Step1 và Step2 – Giả sử chúng ta lừa được client(ở đây là mobile) rằng ServerXXX mới là con server, lừa nốt server(ở đây là Server mà chúng ta cần kết nối đến) rằng ServerXXX mới là client thì sao?

    Bingo!!!

    Tức là thực tế dữ liệu đã gửi đến 1 con ServerXXX trung gian, sau đó forward cho client hoặc server – nghĩa là dữ liệu này hoàn toàn có thể được đọc, được giải mã, được sửa đổi ở server ServerXXX?

    Chúng ta có thể kiểm chứng suy luận này một cách dễ dàng theo cách bên dưới.

    (Mình xin nhấn mạnh lại một lần rằng, đây chỉ là một trò chơi ở level Kiddy có thể dùng trong việc kiểm thử – còn trên thực tế, sẽ tàn khốc hơn thế này rất nhiều. Nên việc chú trọng vào Security khi thiết kế, lập trình ứng dụng là cực kỳ quan trọng)

    MITM attack với WebProxy

    Chuẩn bị

    • Cài Web Proxy trên máy tính: Fiddler (Window), Charles (Mac)
    • Chuẩn bị 1 con điện thoại Android
    • Kết nối Máy tính, Android vào cùng 1 giải mạng
    • Cài đặt proxy của điện thoại Android trỏ vào IP của máy tính: mục đích mọi gói tin đi qua android đều đi qua proxy là máy tính

    Mô hình mạng sẽ như sau

    network

    Do Mobile nhận PC là proxy, nên toàn bộ việc gửi nhận dữ liệu – nói một cách chính xác là: toàn bộ hoạt động liên quan đến internet của Mobile đều bị Proxy – ở đây là PC giám sát Khi mở Webproxy trên máy tính – ở đây mình dùng Fiddler – chúng ta có thể nhìn thấy các gói tin

    Fidder1

    Tất nhiên, các gói tin này nếu dùng HTTPS thì sẽ là gói tin mã hóa – Không có giá trị gì

    Vậy làm sao để giải mã các gói tin này? Easy, follow 4 steps bên dưới nhé

    1. Setting Proxy của điện thoại trỏ vào PC(Giả sử IP của PC là 192.168.0.100)

    setting0

    2. Setting WebProxy để capture HTTPS Connect & Decrypt data

    Vào setting của WebProxy, chọn 2 mục

    • Capture HTTPS CONNECTs
    • Decrypt HTTPS traffic

    setting1

    Sang thẻ connection

    • Port :ở đây mình chọn port 8888 – đây chính là port để setup proxy cho Mobile
    • Chọn Allow remote computers to connect

    setting2

    3. Export Cert của webproxy và cài vào điện thoại

    Export root cert của webproxy sau đó copy vào điện thoại – cài cert như bình thường

    setting2

    Mục đích của việc này chính là để WebProxy và điện thoại có thể "hiểu nhau" – sau hành động mọi kết nối đến internet từ điện thoại – thông qua proxy đã ko còn bí mật – Proxy sẽ nhìn được toàn bộ dữ liệu của điện thoại

    4. Kết quả test thử với việc đăng nhập account Fsoft

    fsoft

    Dù là giao thức HTTPS nhưng dữ liệu username/password vẫn phơi thân ra như ảnh dưới Mọi người có thể thử với 1 số ngân hàng, ví điện tử, không phải ngân hàng/ví điện tử nào cũng áp dụng các cơ chế để phòng tránh MITM attack. Mình đã thử với 1 số ví điện tử/ngân hàng(mà ko tiện kể tên ra) thì thấy dữ liệu dạng này vẫn phơi thân ra mời gọi đầy quyến rũ 😛

    Replay gói tin

    replay

    Một tính năng cực kỳ hay ho của các tool Web Proxy là chúng ta có thể Replay gói tin, thậm chí sửa dữ liệu trước khi replay. Tức là ví dụ Chuyển 10 đồng thì chúng ta có thể sửa lại thành 200 đồng rồi replay gói tin. Tình cờ 1 cách đen đủi bạn code backend ko tính khả năng này là chúng ta đã có 200 đồng rồi. Đây cũng chính là thủ thuật áp dụng để trick điểm một cách quang minh chính đại trên Server GST – Hero. VD mình chơi được 30 điểm thì mình có thể sửa lại dữ liệu thành 50 điểm rồi đẩy lên server bằng tính năng Replay

    Yeah, câu hỏi quan trọng nhất: Phòng chống MITM attack thế nào?

    Client(mobile, web..etc) cần làm gì, Server cần làm gì?

    Nếu bạn là 1 developer, nếu bạn có nhiều hơn 2 năm kinh nghiệm, nếu lĩnh vực chính của bạn là client server, ví điện tử, financial…etc thì chúng ta sẽ buộc phải quan tâm đến vấn đề này

    Mình post bài lấy chỉ tiêu nên chúng ta hẹn nhau ở post sau nhé 😛

    senior

  • Software Architecture: Bắt đầu từ đâu? – Part 1

    Software Architecture: Bắt đầu từ đâu? – Part 1

    Motivation

    Series này hưởng ứng Technopedia, nhưng các bài viết sẽ không phục vụ mục đích dự thi. Đầu tiên mình định viết về Solution Architecture theo quyển Solutions Architect’s Handbook nhưng nghĩ lại thì Software Architecture sẽ hợp lý để bắt đầu hơn.

    Disclaimer

    Bài viết này không nhằm mục đích hướng dẫn hay đề ra một con đường trong sự nghiệp, chỉ là chia sẻ và những gì mình đã trải nghiệm thôi.

    Developer đến Software Architecture

    Mọi người sẽ thắc mắc tại sao lại là Developer chứ không phải Coder. Về cơ bản thì Coder – Người code tức là họ chỉ biết hoặc cũng chỉ muốn code (Ở một phương diện nào đó), còn Developer – Người phát triển phần mềm, tức là họ xử lý tất cả các vấn đề liên quan đến quy trình phát triển phần mềm (Software Development Process) từ nói chuyển với stakeholders, thiết kế ứng dụng (ở một mức độ nào đó), deploy phần mềm, debug, sửa lỗi và cải tiến phần mềm. Một cách đơn giản trong đa số trường hợp Coder tương đương với Junior Developer.

    Ồ!!!

    Vậy Developer sẽ trở thành Software Architecture như nào và điểm khác biệt là gì khi developer đã có thể thiết kế ứng dụng (như mình có nói ở trên). Mình sẽ đưa ra ví dụ như này cho mọi người dễ hình dung:

    Trong một dự án xây dựng hệ thống và phần mềm di động đi kèm một chiếc máy ảnh, người dùng có thể download một số sticker và chỉnh sửa lên bức ảnh của mình, Backend sẽ xây dựng một vài API để phục vụ. Trong trường hợp này Developer đã có thể thiết kế được API, kiến trúc của Backend dựa trên ExpressJS, deploy chúng lên AWS, xử lý memcache để tốc độ phản hồi tốt hơn, người dùng nhận được sticker mới ngay khi chúng được upload.

    Vậy có thể thấy rằng hầu hết công việc đã được Developer xử lý (ở đây tạm thời không phân chia Junior-Middle-Senior nhé) và dự án golive thành công. 5 tháng sau đội dự án bị đập tơi bời từ khách hàng vì bill từ AWS vài $10000 cho mỗi tháng, trong trường hợp này nếu là bạn Software Architecture sẽ xử lý như nào

    (Thực ra có vài cách trong trường hợp này nhưng mình sẽ đưa ra một cách cho thấy sự tương phản nhất) Thay vì đưa trực tiếp API cho Mobile như bạn Software Developer, API sẽ được xử lý như một file tĩnh (JSON-XML) từ S3 cùng với sự hỗ trợ của CDN, file này sẽ được cập nhật khoảng 30′ một lần nếu có sự thay đổi về danh sách sticker. Như vậy người dùng sẽ chờ tối đa khoảng 30′ để có thể nhận được sticker mới, nhưng thay vào đó bill từ AWS sẽ chỉ khoảng $500.

    Vậy bạn Developer có thể biết cái gì tối ưu nhất cho hệ thống nhưng sẽ bị hạn chế khi xử lý các vấn đề tradeoffs-đánh đổi. Trong khi đó Architect sẽ cần biết cả 2.

    Software architecture?

    Đang nói về thiết kế ứng dụng chứ không phải job title nhé :P. Mình sẽ gạch đầu dòng thôi vì chỗ nào cũng có định nghĩa:

    • Các tiêu chuẩn, khái niệm tôn chỉ (gì gì đó) của một hệ thống.
    • Cách tổ chức, các component quan trọng, cách tương tác, những thành phần nhỏ hơn và interface giữa chúng (không cụ thể nhé).

    Về cơ bản thì Software architecture là những hiểu biết chung về thiết kế hệ thống trong dự án phần mềm, tất nhiên sẽ bao gồm cả việc hệ thống được chia nhỏ như nào, và interface giữa chúng là gì nữa, nhưng chỉ dừng lại ở những component và interface mà mọi người đều cần hiểu thôi nhé.

    Ví dụ: chi tiết về việc iOS team lưu trữ password hay load thông tin từ database ở local device như nào sẽ không là một phần trong architecture của cả dự án nhưng việc chỉ ra những gì cần lưu trữ ở local như một lớp caching thì lại có.

    Ông anh Martin Fowler thì định nghĩa khá đơn giản ở đây:

    So, this makes it hard to tell people how to describe their architecture. “Tell us what is important.” Architecture is about the important stuff. Whatever that is.

    Vậy cần những gì để trở thành một Software Architecture? Có rất nhiều bài viết nói về cái này, nhưng mình sẽ chỉ ra một số cái mình nghĩ là quan trọng nhất:

    • Hard skill hay technical skill gì gì đó:
      • Bao giờ cũng là các application architecture phổ biến, pattern, anti-pattern, cái này thì thực ra khá dễ để nhìn ra và dễ tiếp cận
      • Integration architecture, à thì không phải lúc nào cũng xây dựng một cái gì đó từ đầu (from scratch), nên integration luôn là sự lựa chọn tốt.
      • Enterprise architecture, nó liên quan đến tất cả mọi thứ của một công ty, cá thể kinh doanh, từ chiến lược kinh doanh, mô hình vận hành, hoạt động kinh doanh và cả những thứ liên quan đến IT và Infrastructure nữa, nên nếu có bị giới hạn về mặt hiểu biết, mình có thể sẽ không đi sâu về cái này.
    • Soft skill cái này quan trọng này, có thể mọi người sẽ nghĩ đến những thứ khá cao siêu như Leadership hay Organization nhưng mình sẽ đi từ những thứ nhỏ nhỏ trước:
      • Ra quyết định – Decisions, tất nhiên là bạn cần quyết định nhiều thứ, đặc biệt là về kiến trúc hoặc đơn giản hơn là trong team sẽ làm việc với nhau như nào (communication decision)
      • Continuous Delivery, nghe có vẻ là hard skill nhưng thực tế là soft skill. Về cơ bản phần mềm hay hệ thống sẽ vẫn là một thứ gì đấy trừu tượng (ở trên bản vẽ – mượn từ archiecture – kỹ sư trong ngành xây dựng), cho đến khi nó được deploy trên môi trường thực tế. Và khi phần mềm được deploy càng sớm và càng liên tục thì việc chứng mình architecture hợp lý càng nhanh và chính xác, mọi người cũng trưởng thành hơn về mặt thực hành.

    image

    Bài tiếp theo mình sẽ viết tiếp về các skill nhé.

  • GST’s Technopedia – Hướng dẫn nộp bài

    GST’s Technopedia – Hướng dẫn nộp bài

    Các bài viết của event lần này hướng đến việc reuse trong nội bộ GST.

    Do đó, phương thức nộp bài sẽ có chút khác biệt.

    • Tạo bài post mới để nộp bài. Nếu chưa có account, hãy tạo account
    • Các bài viết sẽ tuân theo format markdown, xem thêm hướng dẫn format tại đây.
    • Có thể tham khảo bài sample bằng cách down load file Technopedia-Guildline.
    • Có thể dùng Visual studio code hoặc các trình editor tương tự để edit bài và preview bài (xem hình sample bên dưới).
    • Khi nộp bài thì zip bài lại và attach lên bài post.
    • Với những bài viết tốt thì team editor sẽ publish lên trang (sau khi edit format).

    Event sẽ được trao thưởng hàng tuần.

    Sample về sử dụng Visual studio code và plugin markdownlint

    Upload bài viết dưới dạng file zip:

  • Method Swizzling in Swift

    Method Swizzling in Swift

    Table of Contents

    • Problems?
    • What is method swizziling?
    • Swizzling CocoaTouch class
    • Swizzling custom Swift class
    • Note
    • References

    Problems?

    If you meet one of these situations, how will you handle?

    • Firebase SDK only provides you a function named "showLoginView()" to present a LoginViewController. The problem is all of view controllers in your app use a custom background color? So how can we set background color for LoginViewController?
    • Firebase SDK saves value to UserDataDefault, but you expect that all keys must have a prefix, for example is "FPT". How can you do this?

    Method swizzling comes to rescue

    Defination

    So what is method swizzling?

    Method swizzling is the process of changing the implementation of an existing selector at runtime.

    Speak in a easy-to-understand way, method swizzling acts like swap(a, b) function. It will takes implementation of function 1 and function 2 and swap.

    Swizzling

    Use swizzling to solve the problem:

    So with method swizzling, we can change the implementation of viewDidLoad in LoginViewController to our custom implementation that calls change backgroundColor.

    Swizzling CocoaTouch class

    To swizzle, you just need to follow some steps:

    1. Create a new method with your custom implementation.
    2. Get default method selector reference.
    3. Get new method selector reference.
    4. Use objective-C runtime to switch the selectors.

    Let’s swizzle:

    First, create a demo view controller and create new method with your custom implementation.

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            debugPrint("Call default view did load")
        }
    }
    
    extension UIViewController {
        // 1 
        @objc func viewDidLoadSwizzlingMethod() {
            // 2 
            self.viewDidLoadSwizzlingMethod()
            
            // 3 
            debugPrint("Swizzleeee. Call NEW view did load ")
            view.backgroundColor = .yellow
        }
    }
    
    
    1. Create new method with custom implementation
    2. If you add this line, it will call new implementation first, then call default implementation. If. you don’t add this line, it will call new implentation only.
    3. Your custom implementation

    Next, create a function where the swizzle takes place.

    extension UIViewController {
    ...
         static func startSwizzlingViewDidLoad() {
            // 1
            let defaultSelector = #selector(viewDidLoad)
            let newSelector = #selector(viewDidLoadSwizzlingMethod)
    
            // 2
            let defaultInstace = class_getInstanceMethod(UIViewController.self, defaultSelector)
            let newInstance = class_getInstanceMethod(UIViewController.self, newSelector)
            
            // 3
            if let instance1 = defaultInstace, let instance2 = newInstance {
                debugPrint("Swizzlle for all view controller success")
                method_exchangeImplementations(instance1, instance2)
            }
        }
    }
    1. Create 2 selectors of default method and new method.
    2. Create 2 references of 2 selectors by using class_getInstanceMethod.
    3. Use Objective-C runtime to “swaps implementation” of 2 selectors.

    The final step is call the function startSwizzlingViewDidLoad. We must swizzle before the viewController call it’s default viewDidLoad.
    Here, I will swizzle at AppDelegate to make all ViewControllers in apps change backgroundColor to yellow.

    class AppDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            UIViewController.startSwizzlingViewDidLoad()
            return true
        }
    
        ...
    }

    As you can see, all of our view controllers will be set backgroundColor to yellow color.

    Swizzling custom Swift class

    To use method swizzling with your Swift classes, you just need to do:

    • The methods you want to swizzle must have the dynamic attribute.
    • Flow the steps like swizzle CocoaTouch class
    class GST {
        
        @objc dynamic func workFromHome() {
            print("Working...")
        }
        
        @objc dynamic func swizzleWorkFromHome() {
            print("Playing from home...")
        }
        
        static func startSwizzling() {
            let defaultInstance = class_getInstanceMethod(GST.self, #selector(GST.workFromHome))
            let newInstance = class_getInstanceMethod(GST.self, #selector(GST.swizzleWorkFromHome))
            
            if let instance1 = defaultInstance, let instance2 = newInstance {
                method_exchangeImplementations(instance1, instance2)
            }
        }
    }

    And the results:

    Note

    1. If you swizzle multiple times default method, that default method will have the implementation of the lastest swizzle method.

    Example:

    • You swizzle viewDidLoad with your custom method in your AppDelegate.
    • Firebase swizzle viewDidLoad with its custom method when FirebaseSDK init in your app => after AppDelegate.
    • When a ViewController init => It will takes the implementation of Firebase’s custom method instead of your, because Firebase swizzle after you swizzle.
    1. If you are shipping a framework which is used by hundreds of apps, better not to use swizzling in this case. If you must use swizzling, you should added it to the framework’s document.

    References:

  • GIT – Những lưu ý khi config user name/email cho repo

    GIT – Những lưu ý khi config user name/email cho repo

    Git và các dịch vụ Git như Github, Gitlab đang ngày càng trở nên phổ biến đối với các developer hiện này, hay có thể nói đó là phần không thể thiếu rồi.

    Và đương nhiên, một developer có thể contribute đến nhiều project/repository, và mỗi project có thể dùng một định danh khác nhau. Ví dụ như dùng account email công ty với những project của công ty, hoặc email cá nhân với những project làm thêm, học thêm. Thậm chí có những người được khách hàng cấp account riêng để truy cập vào git riêng của họ.

    Và nếu, developer không quản lý tốt git config cho từng project thì có thể sẽ bị nhầm lẫn email lúc commit lên. Đây là điều rất hay xảy ra nhưng tiếc là ít người để ý.

    Nếu bạn làm trong FSoft, hoặc các công ty đề cao việc security đối với việc dùng email công ty cho các mục đích ngoài công việc, thì có thể nhận được những email warning dạng: bạn đang dùng email công ty trong project git này….hãy cho biết lý do tại sao, và một loạt các câu hỏi khác.

    Tại sao IT lại biết được việc bạn push lên github, gần như ngay lập tức?

    Github có API public các event xảy ra trên dịch vụ này gần như realtime tại: https://api.github.com/events. Và nếu tôi viết 1 con Bot scan liên tục API này và phân tích data thì tôi dễ dàng tìm được thông tin người thực hiện commit đấy lên như email hay user name (đương nhiên là email và user name config thôi)

    Ví dụ như:

    Github public events

    Với sample phía trên, ta dễ dàng scan ra được user name và email của người vừa push lên. Và đương nhiên, con BOT của IT cũng sẽ tóm được ta nếu ta sử dụng email công ty để push lên.

    Tuy nhiên, nhiều người sẽ thắc mắc tại sao với project này, tôi sử dụng email cá nhân để đăng ký, nhưng thông tin push lên lại là email công ty?

    Câu trả lời rất đơn giản, nằm ở chỗ config của git mà thôi.

    Git có 2 loại config chính cho các repo là Global config và Local config. Với những thông tin mà được sử dụng ở tất cả các repo thì thường sẽ config trong Global config. Còn những thông tin riêng của từng project sẽ được config trong Local config của từng dự án.

    Tuy nhiên, rất nhiều developer quên, hoặc không biết đến điều này.

    Cùng xem ví dụ bên dưới.

    Ví dụ về config Git Global trong máy: (open terminal và gõ lệnh git config --list)

    Git global config sample

    Sau khi gõ lệnh thì ta sẽ lấy được tất cả thông tin mà git global config đang setting. (ấn q để thoát ra)

    Ví dụ về 1 project không có config user name và email (dùng lệnh git config --list --local)

    Với project/repo này thì khi commit lên, do thông tin user name và email bị thiếu, nêu git sẽ lấy config default trong Global config để thêm vào. Do đó, nếu bạn đã từng setting email global là email công ty, thì với commit này của bạn, email cũng sẽ là mail công ty, mặc dù repo này bạn không hề đăng ký bằng email công ty. Và IT sờ gáy.

    Vậy, làm thế nào để config thông tin cho từng repo? Ví dụ như dưới

    Cd vào thư mục source, dùng config:

    git config user.name nhathm
    git config user.email [email protected]

    Và như vậy, config đã được áp dụng cho project của bạn, check lại bằng command git config --list --local, nếu 2 dòng cuối cùng hiển thị thông tin đúng như bạn đã config là thành công

    user.name=nhathm
    [email protected]

    Hãy hết sức cẩn thận khi commit nếu như bạn đang tham gia nhiều project khác nhau, sẽ không tốt chút nào nếu repo cá nhân lại bị gắn email công ty, và ngược lại.

    Thanks.

  • Composition over Inheritance

    Composition over Inheritance

    Content

    • Vấn đề về sử dụng Inheritance
    • Disadvantages of Inheritance
    • Composition là gì?
    • Sử dụng Composition để thay thế Inheritance.

    Vấn đề về sử dụng Inheritance

    Inheritance là 1 trong những core concept của OOP. 1 vài lợi ích mà Inheritance mang lại đó là:

    • Code reusebility: Các lớp con có các properties và functions của lớp cha -> Có thể giảm sự duplicate code giữa các lớp con bằng cách đặt các phần code bị duplicate vào lớp cha.
    • Code dễ đọc và dễ hiểu hơn.
    • Inheritance đại diện cho mối quan hệ IS-A relation ship -> Các lớp con có thể thay thế cho lớp cha

    Mặc dù Inheritance được sử dụng rộng rãi, nhưng liệu nó có phải là 1 concept mạnh mẽ nên áp dụng mọi lúc mọi nơi? Hãy cùng suy ngẫm về 1 ví dụ sau:

    • SkyBird và MountainBird là 2 class kế thừa từ class Bird. SkyBird và MountainBird có chung hành vi eat() và fly(), do đó chúng ta đặt 2 phương thức này ở lớp Bird để reuse code.
    • Hành vi jug() là khác nhau nên chúng ta sẽ tự override lại ở lớp con.

    Nghe có vẻ ổn. Bây giờ, khách hàng muốn chúng ta thêm 2 class là WildBird và CloudBird. Chúng có hành vi fly() khác so với lớp Bird, vì vậy chúng ta sẽ override lại hành vi fly(), từ đó có thiết kế:

    Nhưng vấn đề ở đây là WildBird and CloudBird có cùng hành vi fly(). Nếu chúng ta thiết kế như Diagram ở trên thì chúng ta đã tạo ra 1 sự duplicate code hành vi fly giữua lớp WildBird và CloudBird.

    Bạn có thể nghĩ 1 cách để tránh duplicate code đó là tạo 1 class cha cho WildBird và CloudBird, và đặt hành vi fly() chung vào đó, như sau:

    Khá OK. Nhưng giờ khách hàng tiếp tục muốn 1 số hành vi khác như WildBird và SkyBird có chung hành vi eat() mới – khác hành vi eat của Bird, CloudBird và MoutainBird có chung hành vi jug(), …

    Nếu bạn tiếp tục cố gắng tạo ra các lớp cha mới thì bạn sẽ tạo ra 1 kiến trúc ngày càng mở rộng, càng rối cho hệ thống của bạn. Ngoài ra bạn còn phải sửa lại kiến trúc hiện có -> Dẫn đến sửa lại rất nhiều class, code hiện có (Vi phạm nguyên tắc OCP).

    Disadvantages of inheritance

    Giờ thì hãy cùng điểm qua 1 vài nhược điểm của kế thừa:

    • 1 object chỉ có thể kế thừa từ 1 lớp cha.
    • Dễ dàng tạo ra 1 kiến trúc lớn, phức tạp
    • Thay đổi 1 function ở lớp cha -> Ảnh hưởng tới toàn bộ lớp con.
    • Thường thì chúng ta cố gắng nhét tất cả các func chung vào lớp cha -> Dẫn đến lớp cha bị phình to, đồng thời có những func của lớp cha mà lớp con này không cần dùng đến. (Điều này rất dễ thấy trong chính project hiện tại của các bạn).
    • Khi mở rộng source code thường dẫn đến phải thay đổi các code hiện có.

    Composition là gì?

    Khác với Inheritance đại diện cho quan hệ IS-A giữa 2 class, thì Composition đại diện cho quan hệ HAS-A giữa 2 class:

    • Composition có nghĩa là "thành phần". Ở ví dụ trên, Wheel là 1 thành phần của Car, nói cách khác, Car chứa 1 instance kiểu Wheel.
    • Có thể hiểu Composition như việc xếp hình. Thay vì đặt các chức năng vào đối tượng gốc, bạn sẽ tách các chức năng ra thành các đối tượng riêng lẻ, sau đó ghép các thành phần đó lại để bổ sung thêm chức năng cho đối tượng gốc của bạn.
    Thay vì đặt các login run, start,… vào đối tượng gốc Car thì ta sẽ tách ra thành các thành phần riêng lẻ

    Lợi ích của Composition:

    • Giải quyết được vấn đề tạo ra 1 big hirachy khi dùng kế thừa.
    • Dễ dàng reuse code giữa các đối tượng 1 cách linh hoạt.
      Ví dụ: Ta có thể reuse các logic turnOn, turnOff giữa các loại xe mà không cần tạo class cha.
    • Khi thêm mới/ thay đổi các hành vi của đối tượng thì không cần phải thay đổi quá nhiều code hiện có, chỉ cần viết thêm code và thay thế các hành vi mới vào hành vi hiện tại. (Đảm bảo nguyên lí SRP và OCP).
    • Để reuse code mà có liên quan đến UI kéo thả thì sử dụng Inheritance là bất khả thi, còn Composition vẫn có thể giải quyết được 😉

    Áp dụng Composition để giải quyết vấn đề ở đầu bài viết.

    1. Tạo ra các interface cho các hành vi fly và eat lần lượt là IFlyBehaviour và IEatBehaviour.
    2. Các class FlyHighBehaviour và FlyLowBehaviour conform protocol IFlyBehaviour, chúng sẽ khai báo lại phương thức fly() với các hành vi riêng. Ngoài ra có thể tạo thêm các class có phương thức fly khác tùy theo yêu cầu bài toán.
    3. Class EatLeafBehaviour conform protocol IEatBehaviour, khai báo lại phương thức eat riêng. Ngoài ra có thể tạo thêm các class có phương thức eat khác tùy theo yêu cầu bài toán.
    4. WildBird vừa có thể bay và ăn nên nó sẽ chứa 2 instance kiểu IFlyBehaviour và IEatBehaviour; Penguin không thể bay nên chỉ cần chứa 1 instance kiểu IEatBehaviour.

    Code triển khai:

    Khi cần thêm các hành vi fly mới, chỉ cần viết thêm các class conform protocol IFlyBehaviour.
    Khi cần thêm các hành vi eat mới, chỉ cần viết thêm các class conform protocol IEatBehaviour.
    Ví dụ tạo 2 đối tượng WildBird có hành vi Fly khác nhau. Các loài chim với các hành vi fly/eat khác thì chỉ cần khởi tạo class chim đó với các hành vi mong muốn
    Khởi tạo đối tượng Penguin không có hành vi bay, và hành vi ăn là ăn cá.

    Kết luận:

    • Composition đem lại nhiều lợi ích hơn, tuy nhiên không phải là luôn luôn thay thế Inheritance = Composition.
    • Sử dụng Inheritance khi bạn thật sự cần dùng đến nó, chứ không nên chỉ vì mục đích reuse code.

    Reference

    https://betterprogramming.pub/inheritance-vs-composition-2fa0cdd2f939

    Cách sử dụng composition với protocol extension: https://medium.com/commencis/reusability-and-composition-in-swift-6630fc199e16 Cách này khá hay nhưng bị 1 nhược điểm là func của protocol không thể để là private.

  • Dev Container — Everyone should know in the Software development world

    Dev Container — Everyone should know in the Software development world

    Original Post: https://medium.com/techoverbygst/dev-container-everyone-should-know-in-software-development-world-ce028938ed16

    I started working with Docker and Container almost 7 or 8 years ago, but I focused 2 last year on architecting on the Cloud. Docker, known for the motto Build, Ship, Run, changed the Software development world a lot, especially web-based.

    Docker — Build, Ship, and Run

    We know that we can run software with Docker everywhere, and we make the development and production environment closer and closer, but I have thought: Can we make the development environment between developer and developer closer with Docker, even unify?

    I used Docker Compose in almost project; it’s worked well to make a database or messaging broker (Kafka) unified, but except for co-workers’ code, they tend to make the change locally and run it natively with their PC and Workstation, for e.g: run php artisan serve with Laravel or go build && go run with Golang. There are many pain points with many bugs because of so f**king many variables in their environment: Go Version, Php Version, Composer Version.

    Visual Studio Code Remote — Containerswill solve this problem. I know about it at random. I am trying to fork WikiJS for internal use wiki in GST, and WikiJS teams are using Remote Container.

    To run and update code, I have not to install specific required NPM, Database, Vue package, or even Elastic Search. It’s packed with a configured devcontainer.json and simple docker-compose.yml to define the environment and creating process.

    devcontainer link with docker-compose.yml, specific service wiki will be chosen as the development environment, postCreateCommand will run right after containers are created in Docker Compose

    We just need to install Remote Container extension in VSCode and reopen the current project in container view, Extension and Docker will do the rest.

    Wait for the create container process to finish.
    Then we have a fully installed dev environment and are ready to make a new code.

    Happy Coding!

  • Basic CAShapeLayer iOS (P3)

    Basic CAShapeLayer iOS (P3)

    Lâu k gặp chủ đề gì hay ho để viết, ngồi viết nốt bài trong series basic CAShapeLayer đang bỏ dở từ lâu vậy =)) Bài viết này sẽ nói về cách sử animation cơ bản với CAShapeLayer.

    Content

    • Khởi tạo 1 animation
    • Các thuộc tính của animation
    • Lưu í

    Khởi tạo 1 animation

    Khởi tạo 1 animation. 1 animation có dạng CABasicAnimation.

    let animation = CABasicAnimation(keyPath: )

    Bởi vì 1 shapeLayer có rất nhiều thuộc tính khác nhau để áp dụng animation, nên khi khởi tạo 1 CABasicAnimation thì ta phải truyền keyPath vào để xác định xem sẽ thực hiện animation trên thuộc tính nào.
    Có vẻ API này đã rất lâu rồi nên Apple không define keyPath bằng enum 🙁 Nên ta phải truyền keyPath bằng 1 string 🙁
    Tìm trên mạng thì đây là các thuộc tính mà CAShapeLayer support để animate:

    Sau khi khởi tạo 1 animation, chỉ việc add animation đó vào shapeLayer của bạn.

    shapeLayer.add(animation, forKey: nil)

    Các thuộc tính của animation:

    Ta có thể cấu hình thêm cho animation 1 chút bằng các thuộc tính của CABasicAnimation như: duration, fromValue, toValue, timingFunction, fillMode, isRemovedOnCompletion, …

    duration, timingFunction, repeatCount, autoReverse

    • duration: set thời gian chạy animation
    • timingFunction: set gia tốc
    • repeatCount: số lần lặp lại animation
    • autoReverse: có reverse lại sau khi kết thúc animation hay không
    • beginTime: Thời gian bắt đầu chạy animation
    func createSquare() {
        let shapeLayer = CAShapeLayer()
            
        shapeLayer.lineWidth = 2.0
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        let openCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 200),
                                          radius: 60.0,
                                          startAngle: 0.0,
                                          endAngle: CGFloat.pi * 2,
                                          clockwise: true)
            
        shapeLayer.path = openCirclePath.cgPath
        view.layer.addSublayer(shapeLayer)
            
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.beginTime = CACurrentMediaTime() + 0.3
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.duration = 2
        animation.repeatCount = HUGE
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        shapeLayer.add(animation, forKey: nil)
    }

    fromValue, toValue

    Tương tự như giải thích ở bài trước, nó sẽ quyết định xem animation chạy % nào. Dưới đây là lần lượt 2 trường hợp chạy animation với fromValue = 0 và fromValue = 0.5

    fillMode

    Trước hết, hay thay đoạn animation ở trên bằng đoạn animation này và quan sát kết quả:

    let animation = CABasicAnimation(keyPath: "strokeColor")
    animation.beginTime = CACurrentMediaTime() + 2
    animation.fromValue = UIColor.blue.cgColor
    animation.toValue = UIColor.cyan.cgColor
    animation.duration = 2
    animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    shapeLayer.add(animation, forKey: nil)

    Ở ví dụ trên, mình chạy animation đổi màu và bắt đầu sau 3s. Màu của layer lần lượt biến đổi như sau:

    • Đầu tiên shapeLayer có strokeColor là màu đỏ
    • Khi bắt đầu animation thì được set là màu blue, animate đến màu cyan
    • Khi kết thúc animation thì strokeColor lại thành màu đỏ

    Thuộc tính fillMode cho phép bạn kiểm soát behavior của animation tại thời điểm bắt đầu và kết thúc của animation. fillMode gồm: forward, backward, both và removed. Có các tác dụng như sau:

    • removed: giá trị default của fillMode. Effect của animation sẽ bị remove khi animation kết thúc -> Lí do đầu tiên khiến strokeColor quay về thành màu đỏ
    • backward: Hiển thị khung hình đầu tiên của animation ngay lập tức. Ở trường hợp này sẽ hiển thị màu xanh ngay lập tức.
    • forward: Giữ lại khung hình cuối cùng của animation cho đến khi bạn remove animation.
    • both: kết hợp forware và backward.

    set fillMode cho animation = .both và quan sát kết quả:

    isRemovedOnCompletion

    Wait? Tại sao set fillMode = .both hoặc .forward mà vẫn bị reset về màu đỏ ban đầu vậy?

    Việc set fillMode = forward chỉ có tác dụng giữ EFFECT cuối cùng vẫn được giữ ở animation, nhưng animation đã bị remove khỏi layer khi thực hiện xong rồi =))

    Vì vậy để giữ cho animation không bị remove thì bạn sẽ phải set đồng thời cả 2 thuộc tính:

     animation.fillMode = .forward
     animation.isRemovedOnCompletion = false
    Uy tín luôn

    Lưu ý

    #1
    Khi app xuống background thì animation sẽ bị remove khỏi layer -> Các animation sẽ bị mất. Để tránh tình trạng này thì chỉ cần set thuộc tính sau cho animation:

    animation.isRemovedOnCompletion = false

    #2

    • Có rất nhiều keyPath để set cho CABasicAnimation, bạn có thể tham khảo thêm ở đây.
    • Tuy nhiên, chỉ những thuộc tính mình để cập ở trên mới được dùng cho CAShapeLayer.
      Việc dùng các keyPath khác để dùng cho CAShapeLayer có thể đem lại những animation không được như ý muốn: ví dụ như 1 vài animation rotate, …
    • Nếu muốn kết hợp các animation không support CAShapeLayer với các shapeLayer thì có thể trick bằng cách add shapeLayer lên 1 UIView, rồi add animation lên layer của view đó.
    loadingView.layer.addSublayer(shapeLayer)
    let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
    loadingView.layer.add(rotationAnimation, forKey: nil)

    #3
    CABasicAnimation có các delegate animationDidStart và animationDidStop để handle thêm nếu muốn nhưng sẽ k đề cập ở bài viết này.

    #4
    CABasicAnimation thì có 1 vài thuộc tính để custom, và cũng chỉ animate đơn giản được bằng fromValue và endValue.
    Để có thể custom được nhiều hơn thì có thể sử dụng thêm keyFrame Animation cho ShapeLayer. // Có thể mình sẽ viết ở 1 bài khác