ニフクラmBaaSお役立ちブログ

スマホアプリ開発にニフクラmBaaS。アプリ開発に役立つ情報をおとどけ!

既存のカンバンシステムをmBaaSに置き換えてみよう(ワークスペースの保存)

f:id:mbaasdevrel:20180119145425p:plain

既存のWebアプリケーションのバックエンドをmBaaSに置き換えてみる連載になります。一つずつ順番に紹介していきますので、同じようなシステムのリプレイスで参考にしてもらえるはずです。

今回はカンバンシステムの基礎とも言える、カンバン(ここではワークスペース)を保存、復元する処理を作っていきます。なおベースはrafaelodon/kanban: A simple Kanban for daily tasks based on HTML5/JS with local storage.を用いています。

全体の流れ

まず既存システムでは次のように初期表示処理をおこなっています。

$(function (){
  // 省略
  initializeKanbanData();
  renderWorkspacesMenu();
  redrawKanban();
});

initializeKanbanData はカンバンデータの初期化を行っています。ついで初期化されたデータに基づいてメニューを作り(renderWorkspacesMenu)、最後にカンバンを描画しています( redrawKanban )。

初期化の際にはデータを取得していますので、非同期処理になります。そのため、次のように書き換えておきます。 redrawKanban も非同期処理ですが、完了を待って何か処理を行う訳ではないので then は省いています。

$(function (){
  // 省略
  initializeKanbanData()
    .then(() => {
      renderWorkspacesMenu();
      redrawKanban();
    })
});

初期化処理

initializeKanbanDataの処理は元々次のようになっています。 localStorage にて KANBAN_BOARDS = kanban.workspaces が存在するかチェックし、なければ作成しています。作成処理は saveWorkspaces で行っています。その後、タスクについても取得しています。

function initializeKanbanData(){
  if(typeof window.localStorage.getItem(KANBAN_BOARDS) === "undefined" ||
    window.localStorage.getItem(KANBAN_BOARDS) == null || 
    window.localStorage.getItem(KANBAN_BOARDS) == "{}"){
    workspaces = {};
    workspaces[KANBAN_DEFAULT_BOARD_ID] = KANBAN_DEFAULT_BOARD_NAME;
    saveWorkspaces();
  }else{
    restoreWorkspaces();
  }
  
  if(typeof window.localStorage.getItem(getWorkspaceName(currentWorkspace)) === "undefined" || 
    window.localStorage.getItem(getWorkspaceName(currentWorkspace)) == null){  
    tasks = {};
    saveTasks();
  }else{
    restoreTasks();
  }
}

ワークスペースの取得と初期化を行う

まず既存のワークスペースを取得します。この時、countを使うことで件数も調べられます。0件だった(データがない)場合は新しいワークスペースを作成します。作成したら saveWorkspaces を実行して保存します。もしデータがある場合は workspaces にデータを追加します。 workspace.last というのは最後に表示していたワークスペースになります。このフラグが立っているデータは currentWorkspace としてデータを残しておきます。

ワークスペースを取得したら restoreTasks() でタスクを取得します。

function initializeKanbanData(){
  return new Promise((res, rej) => {
    // 既存のワークスペースを取得
    Workspace
      .count()
      .fetchAll()
      .then((results) => {
        // 件数が0 = まだワークスペースが存在しない
        if (results.count == 0) {
          // デフォルトのワークスペースを作成する
          workspaces = {};
          var workspace = new Workspace;
          workspace
            .set('name', KANBAN_DEFAULT_BOARD_NAME)
            .set('id', KANBAN_DEFAULT_BOARD_ID)
            .set('last', true)
          workspaces[KANBAN_DEFAULT_BOARD_ID] = workspace;
          // ワークスペースの保存、更新処理はこの関数がまとめて行います
          return saveWorkspaces();
        }else{
          // データがある場合はworkspacesに入れ直します
          // また、最後に見ていたワークスペースを取っておきます
          for (var i = 0; i < results.length; i += 1) {
            var workspace = results[i];
            workspaces[workspace.id] = workspace;
            if (workspace.last)
              currentWorkspace = workspace;
          }
        }
      })
      .then(() => {
        return restoreTasks();
      })
      .then(() => {
        res();
      })
  });
}

さらに分かりやすく

現状ではなるべく元の処理に合わせて作っていますが、コードが長いので分かりづらくなってしまっています。関数にまとめると次のようになります。Promiseを使った処理は流れが見えづらくなるので、なるべく関数やクラスのメソッドを呼び出すようにして短くするのがいいでしょう。

function initializeKanbanData(){
  return new Promise((res, rej) => {
    // 既存のワークスペースを取得
    getAllWorkSpace()
      .then(results => createOrSetWorkspace(results))
      .then(() => restoreTasks())
      .then(() => res())
  });
}

function getAllWorkSpace() {
  return new Promise((res, rej) => {
    Workspace
      .count()
      .fetchAll()
      .then(results => res(results))
      .catch(err => rej(err))
  });
}

function createOrSetWorkspace(results) {
  // 件数が0 = まだワークスペースが存在しない
  if (results.count == 0) {
    // デフォルトのワークスペースを作成する
    workspaces = {
      KANBAN_DEFAULT_BOARD_ID: getNewWorkSapce()
    };
    // ワークスペースの保存、更新処理はこの関数がまとめて行います
    return saveWorkspaces();
  }else{
    return setWorkspace();
  }
};

function getNewWorkSapce() {
  var workspace = new Workspace;
  workspace
    .set('name', KANBAN_DEFAULT_BOARD_NAME)
    .set('id', KANBAN_DEFAULT_BOARD_ID)
    .set('last', true);
  return workspace;
};

function setWorkspace(results) {
  // データがある場合はworkspacesに入れ直します
  // また、最後に見ていたワークスペースを取っておきます
  for (var i = 0; i < results.length; i += 1) {
    var workspace = results[i];
    workspaces[workspace.id] = workspace;
    if (workspace.last)
      currentWorkspace = workspace;
  }
};

ワークスペースの保存処理

続いてワークスペースを保存する saveWorkspaces についてです。元々は次のようになっています。 localStorage なので簡単です。

function saveWorkspaces(){
  window.localStorage.setItem(KANBAN_BOARDS, JSON.stringify(workspaces));
}

これをmBaaSで書きかえると次のようになります。ここで注意点としては、複数のワークスペースをまとめて保存することがある場合です。非同期処理をループ処理する場合、まず非同期処理を配列(promises)に入れておき、それをPromise.allでまとめて処理するのがお勧めです。Promise.allを使えば非同期処理をすべて処理した上で、結果を配列で返してくれます。

また、mBaaSでは新規保存処理がsave、更新処理はupdateメソッドを呼ぶようになっています。そこで区別せず使えるようにobjectIdの有無によってsaveまたはupdateメソッドを呼ぶようにしています。

処理結果はすべてグローバルなworkspacesという変数に入れています。

function saveWorkspaces(){
  return new Promise((res, rej) => {
    var promises = [];
    for (var id in workspaces) {
      var workspace = workspaces[id];
      var method = typeof workspace.objectId === 'undefined' ? 'save' : 'update'
      promises.push(workspace[method]());
    }
    Promise.all(promises)
      .then((results) => {
        workspaces = {};
        for (var i = 0; i < results.length; i += 1) {
          var workspace = results[i];
          workspaces[workspace.id] = workspace;
        }
        res();
      })
  });
}

ここまでで既存のコードを変更した初期表示処理は完了です。

ワークスペースの追加

次にワークスペースの追加について紹介します。この処理は onClickCreateNewWorkspace で実行されます。元々の内容は次のようになります。

function onClickCreateNewWorkspace(){  
  var workspaceName = prompt(message("workspace_new"));
  if(workspaceName){
    var workspaceId = generateWorkspaceId();
    workspaces[workspaceId] = workspaceName;    
    saveWorkspaces();
    renderWorkspacesMenu();    
    switchToWorkspace(workspaceId);
  }
}

これをmBaaS向けに書き換えると次のようになります。この処理はデータストアのクラス、Workspaceを呼び出している以外は殆ど変わっていないのが分かるかと思います。 saveWorkspaces については前述の通り、非同期処理なので then を使って処理を引き継いでいます。

function onClickCreateNewWorkspace(){  
  var workspaceName = prompt(message("workspace_new"));
  if(workspaceName){
    var workspaceId = generateWorkspaceId();
    var workspace = new Workspace;
    workspace
      .set('name', workspaceName)
      .set('id', workspaceId);
    workspaces[workspaceId] = workspace;
    saveWorkspaces()
      .then(() => {
        renderWorkspacesMenu();
        switchToWorkspace(workspaceId);
      })
  }
}

ワークスペースのリネーム

ワークスペースの名前を変える処理は元々のものと殆ど変わりません。データの持ち方としてidを使っていますのでその部分を変更したのと、 saveWorkspaces が非同期処理になったくらいです。

function onClickRenameWorkspace(){  
  var newWorkspaceName = prompt(message("workspace_rename"));
  if(newWorkspaceName){    
    workspaces[currentWorkspace] = newWorkspaceName;
    saveWorkspaces();    
    redrawKanban();

  }
}
function onClickRenameWorkspace(){  
  var newWorkspaceName = prompt(message("workspace_rename"));
  if(newWorkspaceName){    
    workspaces[currentWorkspace.id] = newWorkspaceName;
    saveWorkspaces()
      .then(() => {
        redrawKanban();
      })
  }
}

ワークスペースの削除

最後にワークスペースの削除処理です。これもリネームと殆ど変わりません。removeWorkspaceはlocalStorageからデータを削除する処理ですが、mBaaSの場合はむしろ不要です。そのため currentWorkspace を削除(delete)処理します。

function onClickRemoveWorkspace(){
  if(confirm(message("confirm_workspace_remove",workspaces[currentWorkspace]))){
    //remove old workspace
    removeWorkspace(currentWorkspace);
    renderWorkspacesMenu();
    switchToWorkspace(KANBAN_DEFAULT_BOARD_ID);
  }
}
function onClickRemoveWorkspace(){
  if(confirm(message("confirm_workspace_remove",currentWorkspace.name))){
    //remove old workspace
    currentWorkspace
      .delete()
      .then(() => {
        renderWorkspacesMenu();
        switchToWorkspace(KANBAN_DEFAULT_BOARD_ID);
      })
  }
}

このようにして既存の既存を少し書き換えるだけでバックエンドをmBaaSに差し替えられました。データをクラウド化することでデータの保全性を高めたり、異なるブラウザ間やチーム内でのデータシェアが簡単にできるようになります。データ処理部分だけ変更すれば、UIやDOM操作部分は触れることなく変更できるのも分かるかと思います。

では次回はタスク表示についてmBaaSに対応していきます。

中津川 篤司

中津川 篤司

NCMBエヴァンジェリスト。プログラマ、エンジニアとしていくつかの企業で働き、28歳のときに独立。 2004年、まだ情報が少なかったオープンソースソフトの技術ブログ「MOONGIFT」を開設し、毎日情報を発信している。2013年に法人化、ビジネスとエンジニアを結ぶDXエージェンシー「DevRel」活動をスタート。