+19

Giải bài toán thay đổi Web UI theo thời gian thực với firebase [OutDated]

Vừa rồi dự án mình làm có gặp một bài toán về push notification. Sau khi ngó ngiêng khắp nơi, thì được 1 anh đẹp trai mách nước cho là “dùng firebase đi” . Thế là mình vùi đầu vào tìm hiểu, và sau vài ngày cũng mạnh dạn xin được viết bài chia sẻ.

1. Bài toán

Giả sử mình là trader, chuyên mua đồng bitcoin lúc giá thấp, rồi bán lúc giá cao, để kiếm tiền chênh lệch. Công việc của mình là ngồi nhìn cái màn hình website từ sáng tới tối, và từ tối tới sáng, chỉ để xem biến động tỷ giá.
Thay vì cứ tầm 5 - 10p, mình lại F5 để refresh/reload/get lại trang. Thì giờ không cần phải làm như vậy nữa, bởi khi có bất cứ thay đổi nào về data trên server, thì giao diện trên web cũng tự động thay đổi theo => quá là thân thiện luôn 🕶
Viết đến đây, tự nhiên lại nhớ về cái ngày đầu học code web, cũng gặp bài toán giống vầy, thế là nghĩ ra cái trò dùng js để hẹn giờ tự reload lại trang sau 1 khoảng thời gian định kỳ. Giờ nghĩ lại thật tù tội TT
// Ngoài ra còn nhiều bài toán khác ví dụ push notification trên trình duyệt web để quảng cáo khuyến mãi, sự kiện cho tất cả user, hoặc một nhóm user.

2. Những gì mình biết về firebase

Mình có search các bài viết về firebase, thấy có khá nhiều kết quả. Các bài viết cũng khá na ná nhau. Nên có lẽ những gì mình viết ra đây, chắc chỉ là những gì khiến mình nhớ 😦

a. Overview

  • Là 1 dịch vụ Realtime Database của Google. Lưu trữ trên google dưới dạng json. (Kể từ giờ mình sẽ viết tắt Realtime Database của Firebase là RDF cho ngắn gọn xúc tích :3 )
  • Về phía client, hiện tại firebase hỗ trợ web (thông qua javascript), Android, iOS. Mình không tìm hiểu về Android, iOS, nhưng mình nghĩ có lẽ chúng cùng logic và cùng mô hình hoạt động.
  • Mô hình chung: dữ liệu sẽ không truyền trực tiếp từ client tới web server, mà sẽ được trung gian qua firebase trước.
  • Sơ sơ về cách hoạt động: firebase cung cấp 1 thư viện js, mình sẽ nhúng cái này vào phía front end, sau đấy sử dụng những hàm đã viết sẵn để lấy dữ liệu trên RDF xuống theo thời gian thực. Còn cách thao tác dữ liệu vào RDF, thì có thể dùng REST API, SDK, Console hoặc chính thư viện JS.

b. Tại sao nên dùng firebase ?

  • Mấy cái realtime này, code socket là được. Nhưng nếu code thì sẽ rất tốn effort, thời gian, công sức, chưa kể vấn đề về lỗi, hiệu năng…
  • Google firebase còn đi kèm các dịch vụ khác: Analytics, Hosting…

c. Những điều mà firebase làm mình vỡ mộng

  • Đầu tiên là nó không hỗ trợ gửi message 1-1 (unicast, từ client này sang client khác). Hiện tại chỉ có gửi message từ 1 client tới tất cả, hoặc từ 1 client tới 1 nhóm các client => Để gửi 1-1 thì cũng có cách lách, nhưng mình cảm thấy không ổn nếu số lượng client nhiều. => Vậy là bài toán trong dự án của mình không dùng được, vì mình muốn push notification giữa 2 user cơ.
  • Authentication user trong firebase không được thực hiện trực tiếp với database truyền thống của web server. Mà thông qua tài khoản google, facebook, github, phone number…
  • Firebase gói free sẽ bị giới hạn, muốn gỡ giới hạn thì trả fee.

d. Tản mạn về firebase

  • Search google thấy bài viết về nó cũng nhiều, tây có, ta có, cũ có, mới có => chắc là đông đảo người dùng.
  • Thấy các bài viết từ năm 2014 toàn domain là firebase, trong khi bây giờ lại là subdomain của google, cộng với việc cuối năm 2016 google mới giới thiệu Firebase qua sự kiện Google IO => đoán già đoán non là chắc 1 sản phẩm nào đó có lâu rồi, mới được Google mua lại.
    => Nói chung chắc là hàng ngon.

3. Demo (hiểu hơn thông qua thực hành nhỏ)

a. Mô hình

(ảnh trên mạng)

  • Mình sẽ viết một Web Application, trong đó phía back end sẽ code bằng Java. Có 2 cách code Java, một là có thể dùng các thư viện như Jersey để thao tác với RDF thông qua REST, hai là dùng bộ SDK cho Java của Firebase. (mình dùng cách 2)
  • Phía front end, sẽ code thuần html, js, jQuery.
  • Mô hình này khá giống với phần 1. Bài toán phía trên. (người quản trị vào trang admin, chỉnh sửa tỷ giá bitcoin, sau đó server xử lý, và dữ liệu thay đổi thời gian thực phía client)

b. Chuẩn bị

  • Đăng nhập vào https://firebase.google.com/ và tạo 1 project (ex: tungtv202-java-dev)
  • Enable API của google, để có thể sử dụng Firebase SDK.
  • Mình muốn dữ liệu RDF của mình, ai cũng có thể xem được, nhưng chỉ có mình mới có quyền chỉnh sửa:
    Trên giao diện web console của firebase, vào Databse > tab RULES, và config như sau:
{
  "rules": {
    ".read": "true",
    ".write": "auth != null"
  }
}
  • Tạo mẫu data thủ công RDF bằng tay trên web console như sau

c. Code front end

  • Import thư viện firebase.js
<script src='https://www.gstatic.com/firebasejs/4.6.0/firebase.js'></script>
  • Tại thời điểm bài viết này, 4.6.0 là version mới nhất. Trong quá trình học firebase theo các example trên mạng, mình thấy có rất nhiều version, 2.x.x và 3.x.x => nếu áp không đúng version có thể dẫn tới một số function không chạy được.
  • Ngoài firebase.js, còn có thể add thêm firebase-app.js, firebase-auth.js, firebase-database.js, firebase-firestore.js, firebase-messaging.js. Tùy thuộc vào bài toán nâng cao tới đâu mà nhúng tới đó js. Hiện tại trong ví dụ của mình, chỉ có view dữ liệu, nên dùng firebase.js là đủ.
  • Khởi tạo firebase app, với format như sau
var config = {
    apiKey: "AIzaSyCSJJYSTfJ0sRXZOE63a_dAcgaymbPtkTE",
    authDomain: "tungtv202-java-dev.firebaseapp.com",
    databaseURL: "https://tungtv202-java-dev.firebaseio.com",
    projectId: "tungtv202-java-dev",
    storageBucket: "tungtv202-java-dev.appspot.com",
    messagingSenderId: "903153003126"
};
firebase.initializeApp(config);

Với các version firebase.js cũ, chỉ cần code var myDataRef = new Firebase("https://framgia-demo.firebaseio.com/bitcoin");

  • Sử dụng database có path bắt đầu từ “bitcoin”
var myDataRef = firebase.database().ref('bitcoin');
  • Khi RDF có bất cứ thay đổi gì, thì sẽ bị bắt sự kiện
myDataRef.on('child_changed', function (snapshot){
    var message = snapshot.val();
    displayRate(message.name, message.text);
});

  • Ngoài childchanged có thể dùng childadded, childmoved, childremoved, để bắt sự kiện là được thay đổi, được thêm mới, được di chuyển hay bị xóa. displayRate(message.name, message.text); là function code bằng js thông thường, để show dữ liệu ra thẻ html. => Code hoàn chỉnh
<html>
<head>
    <title>tungtv202 - firebase demo</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src='https://www.gstatic.com/firebasejs/4.6.0/firebase.js'></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
</head>
<body style="max-width: 800px;">
<div id="messgesDiv">
</div>

<script>
    var config = {
        apiKey: "AIzaSyCSJJYSTfJ0sRXZOE63a_dAcI9ymbPtkTE",
        authDomain: "tungtv202-java-dev.firebaseapp.com",
        databaseURL: "https://tungtv202-java-dev.firebaseio.com",
        projectId: "tungtv202-java-dev",
        storageBucket: "tungtv202-java-dev.appspot.com",
        messagingSenderId: "903153003126"
    };
    firebase.initializeApp(config);
    var myDataRef = firebase.database().ref('bitcoin');

    myDataRef.on('child_changed', function (snapshot) {
        var message = snapshot.val();
        displayRate(message.name, message.text);
    });

    myDataRef.on('child_added', function (snapshot) {
        var message = snapshot.val();
        displayRate(message.name, message.rate);
    });

    function displayRate(name, rate) {
        $('<div/>').text(rate).prepend($('<a/>').text(name + ": ")).appendTo($("#messgesDiv").empty());
        $("#messgesDiv")[0].scrollTop = $("#messgesDiv")[0].scrollHeight;
    }
</script>
</body>
</html>

Note: tại front end, ngoài code được chức năng đọc, hoàn toàn có thể code các chức năng khác như chỉnh sửa, xác thực user, token…vv

d. Code backend server bằng Java

  • Import các thư viện cần thiết với maven
<!-- https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http -->
<dependency>
    <groupId>com.google.auth</groupId>
    <artifactId>google-auth-library-oauth2-http</artifactId>
    <version>0.9.0</version>
</dependency>


<dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-admin</artifactId>
    <version>5.4.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.firebase/firebase-server-sdk -->
<dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-server-sdk</artifactId>
    <version>3.0.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.auth/google-auth-library-oauth2-http -->
<dependency>
    <groupId>com.google.auth</groupId>
    <artifactId>google-auth-library-oauth2-http</artifactId>
    <version>0.9.0</version>
</dependency>

=> code hoàn chỉnh

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class GenKey {

    public static void main(String[] args) throws IOException, InterruptedException {
        // khoi tao ban dau
        FileInputStream  refreshToken = new FileInputStream("framgia-demo.json");
        FirebaseOptions options  = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(refreshToken))
                    .setDatabaseUrl("https://framgia-demo.firebaseio.com/")
                    .build();
        // lua chon path database
        FirebaseApp defaultApp = FirebaseApp.initializeApp(options);
        FirebaseDatabase defaultDatabase = FirebaseDatabase.getInstance(defaultApp);
        DatabaseReference ref = defaultDatabase.getReference();
        DatabaseReference usersRef = ref.child("bitcoin");
        
        // chinh sua data
        Map<String, Bitcoin> bitcoin = new HashMap<>();
        String objectName = new Date().toString();
        bitcoin.put(objectName, new Bitcoin("Bitcoin", "$8000"));
        usersRef.setValueAsync(bitcoin);

        Thread.sleep(20000);
    }
}
  • Trong đó file .json là file xác thực User, tải từ google về, bằng cách bấm vào nút Generate New Private Key tại tab SERVICE ACCOUNTS trong phần Project Setting trên web console.
  • Ngoài hàm setValueAsync, còn các hàm update, push…. Nói chung về ý tưởng thì sẽ có cái dùng để thay đổi 1 object trên RDF, có cái để thêm mới, có cái để xóa…
  • Lúc mình tìm hiểu chạy SDK Firebase cho Java, code y xỳ như trên, nhưng chạy mãi không được, rất là ức chế, mãi về sau mới mò ra được là phải thêm cái Thread.sleep vào, nó mới chạy. Nguyên do là vì kết nối internet, chưa kịp đẩy hết data, thì chương trình java đã bị terminate => Đúng bài bản thì ko nên dùng Thread.sleep, mà dùng:
    Task: https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/tasks/Tasks // Để đơn giản demo, mình không dùng Task

e. Kết quả

Sau khi chạy thử hàm main java trên, thì quay lại giao diện website. Nó đã tự động thay đổi, mà không phải F5, hay Reload gì cả.
Kiểu:

Mình xin dừng bài viết tại đây, thanks for reading! 😄


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí