Software Architecture

Software Architecture adalah top level desain software kita secara keseluruhan. Ekspektasinya adalah untuk ditentukan dari awal, tetapi saya memberikan argumen bahwa karena fitur-fitur dapat ditambahkan atau dikurangi seiring berjalannya waktu, maka Software Architecture dapat berubah seiring berjalannya waktu. Meski begitu, Software Architecture setidaknya secara garis besar sudah harus ditentukan sebelum software dibuat.

Ketika menentukan software architecture, kita harus memikirkan terlebih dulu aplikasi seperti apa yang kita ingin buat. Aplikasi kami, SusunJadwal, merupakan aplikasi yang dibuat untuk menyusun jadwal ujian secara otomatis.

Desain awalnya adalah aplikasi dimana pengguna dapat mengupload file excel berisi ruang dan informasi detil tentang mata kuliah, dan mendapatkan hasil jadwal yang memenuhi constraint yang diatur. Jadwal tersebut kemudian dapat diunduh sebagai file xlsx. Aplikasi ini akan memproses kedua file tersebut, tetapi tidak menyimpannya di server backend setelah selesai. Aplikasi ini sebelumnya juga tidak akan menyimpan file xlsx hasil jadwal akhir. Tetapi karena client meminta fitur penyimpanan excel maka kami mengimplementasikannya. Visi kami adalah ketika pengguna meminta pembuatan jadwal, excel akan di-generate dan disimpan di cloud. Fitur men-generate file tersebut ketika pengguna mengklik tombol unduh tetap ada, agar backend tidak perlu mengirimkan request kepada firebase ketika pengguna ingin mengunduh excel file.

Mengetahui semua itu, ditambah lagi dengan prinsip bahwa kami harus memudahkan development untuk para developers (beberapa anggota kelompok kami bahkan tidak mengerti React, dan juga memitigasi ketika beberapa anggota terpaksa tidak bisa mengerjakan), kami memutuskan untuk menggunakan React dalam frontend dan node.js dalam backend.

Khusus untuk cloud storage, kami sebenarnya bisa memilih cloud storage provider apa saja, karena seharusnya semuanya dapat men-support basic cloud file storage, dimana file dapat diunggah dan diunduh sesuai keinginan. Tetapi kami memilih Firebase, karena Spark Plan-nya, yang gratis, memberikan kami storage yang cukup besar (1 GB) pada plan gratisnya untuk file excel yang berisi jadwal sederhana. Sebenarnya Firebase memiliki fitur yang ekstensif dalam plan gratisnya (saya terkejut bagaimana caranya mereka dapat memberikan fitur sebanyak itu secara cuma-cuma), tetapi karena aplikasi kami cukup sederhana maka kami hanya akan menggunakan fitur cloud file storage-nya saja.Firebase Plan.png

storage.png

Untuk Frontend dan Backend, kami menyimpannya di Heroku Dyno untuk sekarang. Heroku Dyno adalah sebuah container linux ringan dimana sebuah aplikasi dapat disimpan. Alasan lengkap mengapa kami menggunakan Heroku dapat dilihat pada blog post ini, tetapi pendeknya adalah karena gratis, mudah dan men-support aplikasi kami yang berbasis javascript. Selain itu, jika client ingin mengubah domain-nya, klien dapat melakukannya secara cuma-cuma.

Untuk melengkapi penjelasan tentang arsitektur aplikasi kami, berikut adalah diagram arsitektur kami secara keseluruhan.

Untitled Diagram

Jika dilihat pada diagram, aplikasi kami memiliki beberapa use case, yaitu:

  1. Pembuatan jadwal secara otomatis, dan penyimpanan hasil jadwal dalam excel file yang telah dibuat di awan.
  2. Pengunduhan excel file yang telah dibuat.
  3. Display list excel yang telah dibuat dan disimpan di awan
  4. Pengunduhan excel file lawas yang telah disimpan di awan
  5. Pendelete-an excel yang tersimpan pada awan

Untuk use case pertama, front-end hanya perlu berkomunikasi dengan backend. Backend yang melakukan heavy lifting untuk generate schedule dan pengunggahan ke firebase cloud storage. Ini membuat front-end menjadi sederhana untuk didevelop dan saya rasa juga, fitur-fitur berat dari sisi teknis yang tidak perlu terlihat sebaiknya tidak diperlihatkan ke klien yang tidak terlalu tech-savvy (link akan menuju ke blog post tentang persona, yang menjelaskan sedikit lebih jauh tentang ini).

Untuk use-case kedua juga, agar backend tidak perlu berkomunikasi secara eksesif ke awan maka file xlsx akan di-generate secara otomatis. Seharusnya file excel yang sama dengan yang di-generate untuk diunggah ke awan akan diperoleh.

Untuk use-case ketiga, front-end akan melakukan request dua kali: pada awal, dan pada saat file excel baru telah diupload. Sinyal untuk melakukan request akan dikirim ketika frontend menerima response backend untuk use-case pertama.

Untuk use-case terakhir, past generated schedule yang sesuai dengan yang diminta pengguna akan diunduh ke device pengguna.

Dengan node.js, akan ada beberapa library yang dapat kami gunakan untuk men-generate xlsx secara otomatis. Salah satunya adalah ExcelJS.

Kemudian, kami harus menentukan berapa environment yang akan digunakan oleh aplikasi kami. Dengan dibantu oleh asisten dosen, kami memutuskan untuk menggunakan tiga environment

  1. Development Environment (local): Tempat kami menulis kode dan mencoba-coba kode yang kami tulis
  2. Staging Environment (heroku): Tempat kami mendeploy aplikasi untuk dinilai oleh product owner pada sprint review
  3. Production Environment: Tempat kami mendeploy aplikasi untuk digunakan oleh pengguna.

Tentu seiring berjalannya waktu, ini dapat berubah. Ada tanda-tanda bahwa kami harus menambahkan basis data ataupun file storage di masa depan, tetapi itu semua itu tergantung Product Owner dari aplikasi yang kami buat. Tetapi untuk sekarang, aplikasi kami memiliki arsitektur seperti diatas.

Update: Ternyata memang harus ditambahkan file storage. Detil pengimplementasian file storage ditulis diatas.

Bagaimana jika partner tidak ingin datanya berada di data center lain?

Jika kita tidak dapat menggunakan firebase maka ada dua cara yang dapat dilakukan. Yang pertama adalah untuk men-setup sebuah solusi penyimpanan data yang fungsionalitasnya persis dengan apa yang kita butuhkan dari Firebase. Dengan cara tersebut, seharusnya arsitekturnya tidak berubah, hanya saja Firebase digantikan dengan solusi penyimpanan data yang dibuat. Berikut kira-kira arsitekturnya, misalkan Solusi File Storage yang dibuat berdasarkan Node.js:

Untitled Diagram 2

Solusi tersebut membutuhkan sebuah server untuk di-setup dan API untuk download, upload, list dan delete file untuk dibuat.

Solusi kedua yang dapat saya tawarkan, yang lebih mudah untuk dibuat, adalah untuk menyimpan filenya di backend yang telah dibuat. Kita hanya perlu menambahkan API untuk download, list dan delete file. Berikut kira-kira arsitekturnya:Untitled Diagram 3

Heroku Ephemeral Filesystem

Sebelum saya jelaskan lebih jauh tentang solusi yang saya buat, saya akan menjelaskan terlebih dahulu mengapa saya harus menggunakan Firebase jika menggunakan Heroku Dyno. Setiap Heroku Dyno memiliki filesystem yang dapat digunakan untuk menyimpan file yang dibuat secara otomatis oleh aplikasi web yang di-deploy kedalamnya. Tetapi, filesystem tersebut bersifat ephemeral, yang berarti file tersebut akan terhapus ketika Heroku Dyno di-reset karena deployment terbaru ataupun karena normal dyno management yang terjadi setiap sekitar satu hari. Untuk informasi yang lebih lanjut tentang Heroku Ephemeral Filesystem dapat dilihat disini. Karena kita menggunakan solusi yang seluruhnya berada di sistem yang dimiliki oleh partner, maka kita bisa mengubahnya dengan solusi containerization yang berbeda, atau bahkan tidak menggunakan containerization sama sekali (tetapi ada beberapa keuntungan jika menggunakan Docker, yang saya elaborasikan sedikit lebih jauh di sini).

Mengubah Backend agar tidak lagi terhubung dengan Firebase

Jika anda melihat diagram arsitektur pertama yang saya berikan, maka anda dapat melihat bahwa hanya ada satu use-case yang menghubungkan Backend dan Firebase. Fungsionalitas tersebut direfleksikan pada kode dibawah, dimana setelah excel file dibuat, excel file tersebut akan disimpan:

Code snippet dari create_xlsx_table.js

function createXlsx(data_array) {
    let wb = xlsx.utils.book_new()
    let wsName = "Jadwal ujian";

    let data = {};

    register(data, data_array);
    let maxLengths = getMaxLengths(data);

    /* make worksheet */
    let wsData = [
        ['Jadwal kuliah'],
        [],
    ];
    let mergeData = [];
    let [headerXl, dayOutput] = fillHeader(data, mergeData);
    wsData.push(headerXl);
    
    let arrayOutput = [];
    wsData.push(...fillTableData(data, maxLengths, mergeData, arrayOutput));
    
    let ws = xlsx.utils.aoa_to_sheet(wsData);
    
    ws['!merges'] = mergeData;
    xlsx.utils.book_append_sheet(wb, ws, wsName);
    xlsx.writeFile(wb, 'files/output_jadwal_ujian.xlsx');
    // TODO: Replace this with actual name of file
    const nameOfFile = getCurrentDate(); 
    const options = {
        destination: nameOfFile + '.xlsx',
    }
    storageBucket.upload('files/output_jadwal_ujian.xlsx', options);
    return [dayOutput, arrayOutput]
}

Karena kita tidak ingin menggunakan Firebase, maka kita akan menghancurkan koneksi kita dengan firebase, dan menggantinya dengan penyimpanan secara lokal pada Backend. Berikut adalah modifikasi kodenya:

Code snippet dari create_xlsx_table.js

function createXlsx(data_array) {

    let wb = xlsx.utils.book_new()

    let wsName = "Jadwal ujian";

    let data = {};

    register(data, data_array);

    let maxLengths = getMaxLengths(data);

    /* make worksheet */

    let wsData = [

        ['Jadwal kuliah'],

        [],

    ];

    let mergeData = [];

    let [headerXl, dayOutput] = fillHeader(data, mergeData);

    wsData.push(headerXl);

    

    let arrayOutput = [];

    wsData.push(...fillTableData(data, maxLengths, mergeData, arrayOutput));

    

    let ws = xlsx.utils.aoa_to_sheet(wsData);

    

    ws['!merges'] = mergeData;

    xlsx.utils.book_append_sheet(wb, ws, wsName);

    xlsx.writeFile(wb, 'files/output_jadwal_ujian.xlsx');

    const nameOfFile = getCurrentDate(); 

    xlsx.writeFile(wb, 'files/history/' + nameOfFile + ".xlsx")

    return [dayOutput, arrayOutput]

}

Mengubah Frontend agar tidak lagi terhubung dengan Firebase

Kembali ke diagram arsitektur pertama yang saya berikan, maka anda dapat melihat bahwa ada tiga use case yang menghubungkan Firebase dengan Frontend. Use case ini akan kita ubah hubungannya dari Firebase ke Backend. Oleh karena itu, maka harus dibuat terlebih dahulu API yang akan menjadi sasaran request dari Frontend. Berikut adalah file yang mengandung ketiga endpoint yang saya buat untuk menerima requestnya; satu untuk setiap use case:

Kode pada list_file.js

const express = require('express');

const fs = require('fs');

const folderName = 'files/history/'

router = express.Router();

router.get('/', function (req, res, next) {

    //returns file metadata

    fs.readdir(folderName, function (err, files){

        listOfFiles = [];

        if (err){

            console.log('Unable to scan directory: ' + err);

            return 

        } files.forEach(function (file) {

            let filesize = fs.statSync(folderName + file)["size"];

            let pushed = {"name": file, "size": filesize}

            listOfFiles.push(pushed);

        });

        console.log(listOfFiles);

        res.status(200).send({"items": listOfFiles});

    });

});

router.get('/download/:filename', function (req, res, next) {

    filename = folderName + req.params.filename;

    console.log(filename);

    console.log(fs.existsSync(filename));

    if (fs.existsSync(filename)) {

        res.status(200).download(filename);

    } else {

        res.status(500).send({valid: false});

    }

});

router.get('/delete/:filename', function(req, res, next) {

    filename = folderName + req.params.filename;

    console.log(filename);

    console.log(fs.existsSync(filename));

    if (fs.existsSync(filename)){

        fs.unlinkSync(filename);

        res.status(200).send({valid: true});

    } else {

        res.status(500).send({valid: false});

    }

});

module.exports = router;

Agar file ini digunakan, maka saya menambahkan ini ke app.js:

Snippet app.js

var listFile = require('./routes/list_file');

...

app.use('/list_file', listFile);

...

Kemudian, saya harus mengubah kode yang ada pada Frontend untuk melakukan request ke Backend daripada ke Firebase. Berikut adalah kode yang akan saya ubah:

Snippet pada FileDropdown.jsx

getList = async () => {
        var returnedListOfFile = [];
        var totalSizeOfFile = 0;
        var listRef = storage.ref("")
        const results = await listRef.listAll()
        for (let i = 0; i < results.items.length; i++){
            const eachMetadata = results.items[i].getMetadata()
            returnedListOfFile.push(results.items[i]);
            totalSizeOfFile = totalSizeOfFile + eachMetadata.size;
        }
        this.setState({ listOfFiles: returnedListOfFile, totalStorageOccupied: totalSizeOfFile})
        this.setVisible("visible")
    }

    deleteFile = async (name) => {
        console.log(name)
        storage.ref("").child(name).delete()/*.then(function() {
            // File deleted successfully
            //console.log(name + " deleted successfully")
          }).catch(function(error) {
            // Uh-oh, an error occurred!
           // console.log("failure to delete " + name)
          });*/
          await this.setVisible("hidden")
    }

    setVisible = (visibility) => {
        this.setState({ dropdownVisibility: visibility})
    }

    downloadFile = async (name) => {
        window.location.href = await storage.ref("/").child(name).getDownloadURL()
    }

    getListOfFiles() {
        return this.state.listOfFiles;
    }

    render(){
        let listOfFiles = this.getListOfFiles();
        return <>
        <div className = "navbarButton"><Button id = "menuButtonFiles" onClick = {this.state.dropdownVisibility === "visible"? () => this.setVisible("hidden") : this.getList }> <FaBars size={"70px"} /></Button></div>
        <div className = "dropdown" style={{visibility: this.state.dropdownVisibility}}>
            {listOfFiles.map(
            eachFile => <div key = {eachFile.name} id = {eachFile.name}><div>{eachFile.name}</div>
                            <div className = "dropdownButtons"><div><Button className = "downloadButton" onClick = {() => this.downloadFile(eachFile.name)} download> Download </Button>
                            <Button className = "deleteButton" onClick = {() => this.deleteFile(eachFile.name)}> Delete </Button></div></div><hr/></div>
        )}
        <div>{this.state.totalStorageOccupied + 0} / {1073741824 * 5} of storage occupied.</div></div>
        {/*<div> Upload Progress { this.state.progress}</div>
        <div>{ "unknown"} / 1073741824 bytes downloaded per day</div>
        <div>{ "unknown" } / 20k upload operations </div>
        <div>{ "unknown" } / 50k download operations</div>*/}
        </>
    }
}

Berikut adalah perubahannya:

Snippet pada FileDropdown.jsx

 getJSON(url){

        return new Promise(function (resolve, reject){

            var xhr = new XMLHttpRequest();

            xhr.open('GET', url, true);

            xhr.responseType = 'json';

            xhr.onload = function() {

                var status = xhr.status;

                if (status === 200){

                    resolve(xhr.response)

                } else {

                    reject(status);

                }

            };

            xhr.send();

        })

    }

    

    // from https://web.archive.org/web/20200502183806/http://janhesters.com/updater-functions-in-setstate/

    // async from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

    getList = async () => {

        var returnedListOfFile = [];

        var totalSizeOfFile = 0;

        var results = await this.getJSON(this.props.listUrl);

        //Get this function call to give the results synchronously so that the result will be set to data before running the for loop.

        for (let i = 0; i < results.items.length; i++){

            const eachMetadata = results.items[i]

            returnedListOfFile.push(eachMetadata);

            totalSizeOfFile = totalSizeOfFile + eachMetadata.size;

        }

        this.setState({ listOfFiles: returnedListOfFile, totalStorageOccupied: totalSizeOfFile})

        this.setVisible("visible")

    }

    deleteFile = async (name) => {

        console.log(name)

        this.getJSON(this.props.listUrl + "/delete/" + name)

        await this.setVisible("hidden")

    }

    setVisible = (visibility) => {

        this.setState({ dropdownVisibility: visibility})

    }

    downloadFile = async (name) => {

        this.getJSON(this.props.listUrl + "/download/" + name)

    }

    getListOfFiles() {

        return this.state.listOfFiles;

    }

    render(){

        let listOfFiles = this.getListOfFiles();

        return <>

        <div className = "navbarButton"><Button id = "menuButtonFiles" onClick = {this.state.dropdownVisibility === "visible"? () => this.setVisible("hidden") : this.getList }> <FaBars size={"70px"} /></Button></div>

        <div className = "dropdown" style={{visibility: this.state.dropdownVisibility}}>

            {listOfFiles.map(

            eachFile => <div key = {eachFile.name} id = {eachFile.name}><div>{eachFile.name}</div>

                            <div className = "dropdownButtons"><div><Button className = "downloadButton" onClick = {() => this.downloadFile(eachFile.name)} download> Download </Button>

                            <Button className = "deleteButton" onClick = {() => this.deleteFile(eachFile.name)}> Delete </Button></div></div><hr/></div>

        )}

        <div>{this.state.totalStorageOccupied + 0} / {1073741824 * 5} of storage occupied.</div></div>

        </>

    }

}

Mengingat cara pengimplementasiannya beda, maka kodenya tidak sama persis dengan pengimplementasian ketika ada Firebase. Tetapi, dengan kode yang saya buat, seharusnya layanan dapat berfungsi sesuai keinginan.

Anda mungkin melihat bahwa ada tambahan metode getJSON(url) yang saya buat. Itu sebenarnya adalah metode yang membuat request kepada sebuah endpoint dan memberikan JSON yang di-return. JSON tersebut bisa diproses menjadi suatu variabel, ataupun tidak tergantung dari kode selanjutnya. Anda juga mungkin melihat bahwa ada this.props.listUrl untuk semua link yang diberikan. Hal tersebut dikarenakan kami hanya ingin men-set linknya di satu file js yang sama, yaitu App.js dan mengoper setting tersebut ke komponen yang membutuhkannya. Berikut adalah isi App.js aplikasi Frontend kami:

import React from 'react';

import './App.css';

import PageContainer from './components/PageContainer.jsx';

const BACKEND_URL = "http://localhost:3000"

//const BACKEND_URL = "https://susun-jadwal-backend-staging.herokuapp.com"

function App() {

  return (

    <PageContainer

      schedulePostUrl={BACKEND_URL + "/upload/schedule"}

      roomsPostUrl={BACKEND_URL + "/upload/room"}

      tableGetUrl={BACKEND_URL + "/get_table"}

      downloadUrl={BACKEND_URL + "/download"}

      listUrl={BACKEND_URL + "/list_file"}

    />

  );

}

export default App;

Karena backend merupakan localhost, maka backend url yang di-set adalah localhost:3000.

Men-setup docker swarm

Jika anda melihat kembali diagram yang saya buat, anda akan mengetahui bahwa solusi yang saya buat adalah untuk membuat container untuk setiap service. Menggunakan satu file docker-compose.yml dan Dockerfile untuk setiap service, maka saya dapat menggunakan satu command saja untuk mem-build image, yaitu

docker-compose build

Berikut adalah Dockerfile yang saya buat untuk setiap service dan file docker-compose.yml yang saya buat.

Dockerfile (identik untuk setiap file)

# pull official base image
FROM node:slim
# set working directory
WORKDIR /app
# install app dependencies
COPY package.json /app
RUN npm install --silent

# add app
COPY . /app

EXPOSE 3000

# start app
CMD ["npm", "start"]

docker-compose.yml

version: "3.7"

services:

  frontend:

    build:

      context: ./frontend

      dockerfile: Dockerfile

    image: manullangc/susunjadwal-frontend:latest

    ports: 

      - 3001:3000

    stdin_open: true

  backend:

    build:

      context: ./backend

      dockerfile: DockerFile

    ports: 

      - 3000:3000

    stdin_open: true

    volumes:

      - files:/app/files/history

    image: manullangc/susunjadwal-backend:latest

volumes:

  files:

Sedikit penjelasan tentang Dockerfile tersebut. Dockerfile tersebut akan melakukan hal-hal ini:

  1. Mengambil Image Node yang terminim (:slim). Ini bisa juga diubah ke versi yang anda inginkan
  2. Set Working Directory, yang berarti membuat folder baru dimana kita akan mengkonstruksi Docker Container
  3. Menyalin package.json ke Working Directory dan menginstall Dependencies
  4. Menyalin file-file yang berhubungan ke aplikasi yang anda buat ke Working Directory
  5. Meng-expose port 3000 sehingga bisa diakses dari luar (sangat penting agar dapat melakukan port forwarding)
  6. Menjalankan command yang ada di dalam array. Docker akan menganggap array satu baris input ke terminal tersebut dan setiap indeks array diberi jeda spasi.

Kemudian berdasarkan file docker-compose.yml, maka saat docker-compose build dijalankan, maka hal ini akan terjadi:

  1. Kita men-set versi file format dari compose file yang kami buat. Untuk lebih lanjutnya dapat dilihat di sini tetapi pendeknya adalah: versi mempengaruhi kompatibilitas dengan Docker Engine yang digunakan. Karena kami menggunakan Docker Engine 18.06.0, maka kami menggunakan version 3.7 sebagai versi file format kami.
  2. Kami men-set kedua service kami yaitu frontend dan backend
  3. Kami men-set build (ini sama dengan command Docker Build) dengan konteks yaitu folder dimana service kami berada dan Dockerfile yang digunakan untuk membantu proses Build tersebut. Sebenarnya bisa saja tidak menggunakan Dockerfile, tetapi kita harus menulis command sendiri di docker-compose.yml yang dibuat.
  4. Kemudian kita melakukan port forwarding. Ini penting untuk dilakukan karena default port untuk Node.js adalah 3000, dan jika anda belum tahu, setiap port hanya akan dapat dihubungkan ke satu aplikasi/service. Oleh karena itu, untuk salah satu service, kita harus menghubungkannya ke port lain. Men-set port akan mem-port forward aplikasi tersebut dari port dimana aplikasi tersebut berjalan pada Docker Container ke port local ataupun server dimana docker container dijalankan.
  5. stdin_open: true ditambahkan karena menurut dokumentasi hal tersebut akan menyelesaikan masalah-masalah common yang dialami jika melakukan containerization aplikasi node.
  6. Mensetup nama image yang akan dihasilkan dari proses build tersebut.
  7. Menset directory yang akan menjadi mount point dari volume
  8. Mendefinisikan volume yang ada

Anda mungkin melihat bahwa saya menaruh volume di situ. Saya melakukan hal itu karena jika tidak dibuat volume, maka data akan tertulis pada Container Layer, dan file yang tertulis disitu akan hilang ketika Container tersebut di-reset (mirip dengan konsep filesystem yang dimiliki oleh Heroku Dyno). Oleh karena itu, saya menggunakan volume agar file tersebut dapat tersimpan meski container di-reset karena alasan apapun. Untuk mengetahui lebih lanjut tentang volume, anda dapat membuka tautan berikut ini.

Dengan asumsi bahwa file sudah berada di server partner yang OSnya adalah Windows Server 2019, dengan docker desktop telah diinstall (atau Linux dengan Docker engine telah terinstal), domain telah di-setup untuk IP address dari server, dan port 3000 telah diforward ke port 80, maka kita dapat melakukan  hal ini untuk menjalankan aplikasinya:

docker swarm init
docker stack deploy -c docker-compose.yml susunjadwal

Sedikit penjelasan, docker swarm init akan membuat mesin tersebut menjadi manager dari swarm yang dibuat, kemudian command kedua akan mendeploy proyek ke swarm tersebut.

Setelah itu, anda dapat langsung mengakses servernya dengan membuka domain yang telah anda setup untuk IP address dari server tersebut.

Anda dapat men-clone project saya, yang merupakan fork dari build yang sedang dikerjakan pada saat itu, dengan mengklik link berikut, dan menggunakan command yang sama untuk menjalankannya di komputer anda.

Untuk mengetahui bagaimana cara kami men-setup CI/CD untuk aplikasi kami, anda dapat mengakses tautan ini.

Penutup

Seperti yang telah didemonstrasikan di atas, karena kita mengetahui bagaimana setiap komponen disusun dan hubungannya antara satu komponen dengan komponen yang lainnya, kita bisa dengan sederhana menggantikan Firebase dengan suatu komponen yang memiliki fungsionalitas identik atau, dan juga menggabungkannya ketika kita merasa bahwa komponen tersebut dapat digabungkan. Komponen yang memiliki cara berkomunikasi yang serupa dapat dihubungkan jika dibutuhkan, dan hubungannya dapat diubah, seperti halnya saya menambahkan fungsionalitas dari hubungan antara frontend dan backend pada saat saya menggabungkan file storage dengan backend.

Manfaat memiliki Software Architecture adalah untuk membantu kita untuk memvisualisasikan susunan aplikasi kita secara keseluruhan, dan juga untuk membantu kita ketika ingin melakukan modifikasi di masa depan. Oleh karena itu, ada pentingnya jika kita membuat arsitektur perangkat lunak yang kita akan buat pada saat itu di awal pengembangan perangkat lunak, meski nantinya kita mungkin akan mengubahnya.

Leave a comment