Slack Next-gen Platform – Datastores

This is translation of  “Slack Next-gen Platform – Datastores written by Kaz (SDK Engineering & DevRel at Slack), I do not include any personal opinion here except guessing when meaning of original text is not clear for me)

이 글은 슬랙의 SDK Engineering / DevRel 인 Kaz 의 글을 번역한것입니다. 저의 개인 의견은 들어가지 않으며, 일부 원문의 의미가 애매한 경우에만 부연 설명을 달았습니다.


이번 튜토리얼 에서는, Slack 의 차세대 플랫폼에서 실행되는 앱 내부에서 데이터스토어를 어떻게 사용하는지 배워보겠습니다. (데이터스토어 = 데이터베이스 라고 이해하심 될듯 합니다.)

Prerequisites

차세대 플랫폼을 접하는 것이 처음이시라면, 이전의 튜토리얼인 “The Simplest Hello World” 부터 읽어주세요. 요약하면 유료 Slack 워크스페이스와 해당 워크스페이스에서 “beta feature” 를 사용할 수 있는 권한이 필요합니다. 가지고 계시다면 Slack CLI 와 워크스페이스를 연결하기만 하면 됩니다.

준비되었다면 앱을 한번 만들어보도록 하죠. 시작해보겠습니다.

Create a Blank Project

slack create 명령어를 사용하여, 새로운 프로젝트를 시작할 수 있습니다. 이 튜토리얼에서는 아무것도 없는 상태에서 앱을 만들어보겠습니다. “Blank Project” 를 선택하여 주십시요.

$ slack create
? Select a template to build from:

  Hello World
  A simple workflow that sends a greeting

  Scaffolded project
  A solid foundational project that uses a Slack datastore

> Blank project
  A, well.. blank project

  To see all available samples, visit github.com/slack-samples.

프로젝트가 생성되면, slack run 명령어가 정상적으로 동작하는지 확인해보세요. 이 명령어는 “dev” 버전의 앱을 워크스페이스에 설치합니다. 이 앱의 봇 유저가 생성되고, 이 봇은 API 호출을 위한 봇 토큰 값을 가지고 있습니다.

$ cd affectionate-panther-654
$ slack run
? Choose a workspace  seratch  T03E94MJU
   App is not installed to this workspace

Updating dev app install for workspace "Acme Corp"

  Outgoing domains
   No allowed outgoing domains are configured
   If your function makes network requests, you will need to allow the outgoing domains
   Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
  seratch of Acme Corp
Connected, awaiting events

Connected, awaiting events 로그 메세지를 보신다면, 이 앱이 성공적으로 워크스페이스에 연결된 것입니다. “Ctrl +C” 를 눌러 로컬 앱 프로세스를 종료합니다.

이번 튜토리얼에서는 CLI 를 사용해서 데이터스토어와 통신하는 방법과 펑션 코드를 통해서 통신하는 방법에 대해서 배워볼 것입니다.

Add Datastores to Your App

첫번째 단계는 앱에 데이터스토어를 추가하는 것입니다. tasks.ts 라는 이름의 파일을 만들고 다음 코드를 저장합니다.

import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts";

export const datastore = DefineDatastore({
  name: "tasks",
  // The primary key's type must be a string
  primary_key: "id",
  attributes: {
    id: { type: Schema.types.string, required: true },
    title: { type: Schema.types.string, required: true },
    description: { type: Schema.types.string }, // optional
    due: { type: Schema.types.string }, // optional
  },
});

다른 가능한 옵션들을 살펴보실려면 다음 링크를 참조 하세요. https://api.slack.com/future/datastores

다음으로, 데이터스토어를 manifest.ts 에 추가합니다. 데이터스토어 리스트 프로퍼티에서 임포트된 데이터스토어 정의를 사용할 수 있습니다. 그리고 datastore:read / datastore:write 권한을 botScopes 에 넣어주어야 데이터스토어로의 쿼리를 할 수 있습니다.

import { Manifest } from "deno-slack-sdk/mod.ts";
import { datastore as Tasks } from "./tasks.ts";

export default Manifest({
  name: "nifty-capybara-954",
  description: "Datastore Example",
  icon: "assets/default_new_app_icon.png",
  datastores: [Tasks],
  botScopes: [
    "commands",
    // Necessary for accessing datastores
    "datastore:read",
    "datastore:write",
  ],
});

다 되었으면 slack run 을 실행하여 Slack 호스팅 인프라에 변경된 점을 반영하는 것을 잊지 마세요.

Use Datastores via CLI Commands

쿼리 실행과 데이터 처리를 어떻게 하는지 배워보기 위해서 slack datastore 네임스페이스 내의 서브 명령어들을 사용해보도록 하겠습니다. slack datastore –help 를 입력하면 가능한 서브 명령어들이 나옵니다.

$ slack datastore --help
Query an App Datastore

USAGE
  $ slack datastore <subcommand> [flags]

SUBCOMMANDS
  delete      Delete a datastore item. Docs: https://api.slack.com/future/
  get         Get an item from the slack datastore. Docs: https://api.slack.com/future/
  put         Create/Update an App Datastore. Docs: https://api.slack.com/future/
  query       Query for datastore items. Docs: https://api.slack.com/future/

FLAGS
  -h, --help   help for datastore

GLOBAL FLAGS
      --apihost string     Slack API host
  -f, --force              ignore warnings and continue executing command
  -l, --local-run run      use the local run app created by the run command.
  -r, --runtime string     project's runtime language: deno, deno1.1, deno1.x, etc (Default: deno)
  -s, --skip-update        skip checking for latest version of CLI
      --slackdev           use the Slack Dev API (--apihost=dev.slack.com)
  -v, --verbose            when set print debug logging
  -w, --workspace string   use a specific workspace by domain name

EXAMPLE
  $ slack datastore put '{"datastore": "todos", "app": "A0123A45BCD", "item": {"id": "42", "description": "Create a PR", "status": "Done"}}'

ADDITIONAL HELP
  $ slack datastore <subcommand> --help for more information about a specific command.

  For more information, read the documentation: https://api.slack.com/future

이 튜토리얼 내에서 사용할 모든 명령어들의 인자는 단순한 JSON 데이터 문자열 입니다. 만약 “tasks” 데이터스토어에서 데이터를 가지고 오고 싶다면 CLI 명령어의 인자는 {“datastore”: “tasks”} 가 될 것 입니다.

호스팅 서버 사이에 데이터스토어가 생성되어있는지 확인하기 위해서 slack datastore query ‘{“datastore”: “tasks”}’ 를 실행해봅시다.

$ slack datastore query '{"datastore": "tasks"}'
? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Retrieved 0 items from datastore: tasks

To create or update existing items run slack datastore put [item]

쿼리는 정상적으로 실행되었습니다. 그렇지만 아직은 아무런 데이터도 없지요. 명령어 결과물에서 제안하는 바와 같이 데이터스트어에 새 열을 넣어보도록 하죠.

Insert Data

데이터 생성을 위한 인자들은 다음과 같습니다.

{
  "datastore": "tasks",
  "item": {
    "id": "1",
    "title": "Make a phone call"
  }
}

slack datastore put ‘{“datastore”: “tasks”, “item”: {“id”: “1”, “title”: “Make a phone call”}}’ 을 실행하여 새로운 열을 삽입합니다.

$ slack datastore put '{"datastore": "tasks", "item": {"id": "1", "title": "Make a phone call"}}'

? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Stored below record in the datastore: tasks

{
  "id": "1",
  "title": "Make a phone call"
}
To inspect the datastore after updates, run slack datastore query [expression]

새로운 레코드가 저장되었습니다. 다시 쿼리를 실행하여 데이터스토어가 변경되었는지 확인해봅니다.

$ slack datastore query '{"datastore": "tasks"}'
? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Retrieved 1 items from datastore: tasks
Talk
{
  "id": "1",
  "title": "Make a phone call"
}
To create or update existing items run slack datastore put [item]

레코드가 나오는 것을 볼 수 있습니다.

“put” 오퍼레이션에 대해서 알아둘 몇가지가 있습니다.

첫번째로, “put” 오퍼레이션을 실행할 때, 아이템의 프로퍼티 이름을 꼭 확인하시기 바랍니다. 만약 유효하지 않은 이름을 아이템에 넣게 되면, API 호출은 성공하고 에러코드도 반환되지 않지만, 이 프로퍼티들은 무시됩니다.

또한 데이터스토어의 주키의 타입으로 반드시 Schema.types.string 을 사용해야 합니다. 현 시점에서, 주키를 다른 타입으로 쓰는 것은 기술적으로는 가능하지만, “put” 오퍼레이션이 정상적으로 동작하지 않습니다. 그래서 결국 데이터스토어를 재생성 해야하는 일이 발생합니다.

Run a Query

이미 쿼리를 실행해보셨지만, 좀 더 현실적인 쿼리를 실행하여 데이터를 가져와 보겠습니다. Slack 의 데이터스토어에서는 개발자분들이 Amazon DynamoDB 쿼리 구문을 사용할 수 있도록 해두었습니다. 특히 expression, expression_attributes, expression_values 값이 쿼리와 함께 전달되어야 합니다.

다음 조건으로 쿼리를 실행해보겠습니다.

{
  "datastore": "tasks",
  "expression": "begins_with(#title, :title)",
  "expression_attributes": {"#title": "title"},
  "expression_values": {":title": "Make a "}
}

다음과 같은 결과물이 나올 것입니다.

$ slack datastore query '
{
  "datastore": "tasks",
  "expression": "begins_with(#title, :title)",
  "expression_attributes": {"#title": "title"},
  "expression_values": {":title": "Make a "}
}
'
? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Retrieved 1 items from datastore: tasks

{
  "id": "1",
  "title": "Make a phone call"
}
To create or update existing items run slack datastore put [item]

다음은 주키를 쿼리하는 예제 입니다.

$ slack datastore query '
{
  "datastore": "tasks",
  "expression": "#id = :id",
  "expression_attributes": {"#id": "id"},
  "expression_values": {":id": "1"}
}
'

? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Retrieved 1 items from datastore: tasks

{
  "id": "1",
  "title": "Make a phone call"
}
To create or update existing items run slack datastore put [item]

만약 쿼리가 어떤 열과도 맞지 않는다면 제로가 리턴됩니다.

$ slack datastore query '
{
  "datastore": "tasks",
  "expression": "#id = :id",
  "expression_attributes": {"#id": "id"},
  "expression_values": {":id": "999"}
}
'
? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Retrieved 0 items from datastore: tasks

To create or update existing items run slack datastore put [item]

Update an Existing Row

기존의 열을 CLI 명령어를 통해 업데이트 해보도록 하겠습니다. 새로운 열을 추가하는 것과 다른 것 없습니다. “put” 오퍼레이션와 같은 주키를 사용하여 업데이트를 해보죠

$ slack datastore put '{"datastore": "tasks", "item": {"id": "1", "title": "Make a phone call to Jim", "due": "Dec 18"}}'

? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Stored below record in the datastore: tasks

{
  "due": "Dec 18",
  "id": "1",
  "title": "Make a phone call to Jim"
}
To inspect the datastore after updates, run slack datastore query [expression]

Delete a Row

다음은 주키를 사용하여 “delete” 오퍼레이션을 해서 열을 삭제해보겠습니다. 이때 인자에는 JSON 데이터의 탑레벨의 주키가 반드시 있어야 합니다. 무슨말이냐 하면 {“datastore”: “tasks”, “item”: {“id”: “1”}} 형태가 아니라 {“datastore”: “tasks”, “id”:”1″}. 이어야 한다는 것입니다.

$ slack datastore delete '{"datastore": "tasks", "id":"1"}'

? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch  T03E94MJU
   nifty-capybara-954 A04G9B1SFAB

🎉  Deleted from datastore: tasks

primary_key: 1
To inspect the datastore after updates, run slack datastore query [expression]

Slack 데이터스토어에 대한 4가지 오퍼레이션에 대해서 배워보았습니다. 전문가가 되셨네요!

Use Datastores via Functions

가끔씩 쿼리를 실행하거나, 최초에 데이터를 임포트 할때에는 CLI 를 사용하는 것이 편리한 방법이지만, 실제로 데이터스토어를 사용하여 어떤 의미있는 일을 할려면, 펑션 코드로 해야 합니다. 아래는 위에서 언급한 4가지 오퍼레이션을 코드를 통해서 하는 예제입니다. function.ts 파일로 저장합시다.

import { DefineFunction, SlackFunction } from "deno-slack-sdk/mod.ts";
// Add "deno-slack-source-file-resolver/" to "imports" in ./import_map.json
import { FunctionSourceFile } from "deno-slack-source-file-resolver/mod.ts";

export const def = DefineFunction({
  callback_id: "datastore-demo",
  title: "Datastore demo",
  source_file: FunctionSourceFile(import.meta.url),
  input_parameters: { properties: {}, required: [] },
  output_parameters: { properties: {}, required: [] },
});

export default SlackFunction(def, async ({ client }) => {
  const creation = await client.apps.datastore.put({
    datastore: "tasks",
    item: { "id": "1", "title": "Make a phone call to Jim" },
  });
  console.log(`creation result: ${JSON.stringify(creation, null, 2)}`);
  if (creation.error) {
    return { error: creation.error };
  }

  const query = await client.apps.datastore.query({
    datastore: "tasks",
    expression: "#id = :id",
    expression_attributes: { "#id": "id" },
    expression_values: { ":id": "1" },
  });
  console.log(`query result: ${JSON.stringify(query, null, 2)}`);
  if (query.error) {
    return { error: query.error };
  }

  const modification = await client.apps.datastore.put({
    datastore: "tasks",
    item: { "id": "1", "title": "Make a phone call to Jim", "due": "Dec 18" },
  });
  console.log(`modification result: ${JSON.stringify(modification, null, 2)}`);
  if (modification.error) {
    return { error: modification.error };
  }

  const deletion = await client.apps.datastore.delete({
    datastore: "tasks",
    id: "1",
  });
  console.log(`deletion result: ${JSON.stringify(deletion, null, 2)}`);
  if (deletion.error) {
    return { error: deletion.error };
  }

  return { outputs: {} };
});

이 펑션을 실행하기 위해서, 간단한 워크플로와 트리거를 만들어보겠습니다. 다음 코드를 workflow_and_trigger.ts 파일로 저장합니다.

import { DefineWorkflow } from "deno-slack-sdk/mod.ts";

export const workflow = DefineWorkflow({
  callback_id: "datastore-demo-workflow",
  title: "Datastore Demo Workflow",
  input_parameters: { properties: {}, required: [] },
});

import { def as Demo } from "./function.ts";
workflow.addStep(Demo, {});

import { Trigger } from "deno-slack-api/types.ts";
const trigger: Trigger<typeof workflow.definition> = {
  type: "webhook",
  name: "Datastore Demo Trigger",
  workflow: `#/workflows/${workflow.definition.callback_id}`,
  inputs: {},
};
export default trigger;

manifest.ts 에 워크플로 추가하는거 잊지 마시구요

import { Manifest } from "deno-slack-sdk/mod.ts";
import { datastore as Tasks } from "./tasks.ts";
// Add this
import { workflow as DemoWorkflow } from "./workflow_and_trigger.ts";

export default Manifest({
  name: "nifty-capybara-954",
  description: "Datastore Example",
  icon: "assets/default_new_app_icon.png",
  datastores: [Tasks],
  workflows: [DemoWorkflow], // Add this
  botScopes: [
    "commands",
    // Necessary for accessing datastores
    "datastore:read",
    "datastore:write",
  ],
});

workflow_and_trigger.ts 의 소스코드를 사용하여 webhook 트리거를 만들겠습니다.

$ slack triggers create --trigger-def ./workflow_and_trigger.ts
? Choose an app  seratch (dev)  T03E94MJU
   nifty-capybara-954 (dev) A04G9B1SFAB


Trigger created
   Trigger ID:   Ft04GPR8GMGT
   Trigger Type: webhook
   Trigger Name: Datastore Demo Trigger
   Webhook URL:  https://hooks.slack.com/triggers/T***/***/***

자 이제 HTTP 요청을 보내서 워크플로를 트리거 할 수 있습니다. 요청을 보내기전에 터미널에서 slack run 이 실행중인지 확인하세요.

$ curl -XPOST https://hooks.slack.com/triggers/T***/***/***
{"ok":true}%

slack run 이 실행중인 터미널 윈도우에서 보면, 다음과 같은 콘솔 결과물을 볼 수 있습니다.

2022-12-27 13:45:22 [info] [Fn04GSCL7DQC] (Trace=Tr04GHB2TXKQ) Function execution started for workflow function 'Datastore Demo Workflow'
2022-12-27 13:45:22 [info] [Wf04GPULB35Y] (Trace=Tr04GM0VNXKP) Execution started for workflow 'Datastore Demo Workflow'
2022-12-27 13:45:22 [info] [Wf04GPULB35Y] (Trace=Tr04GM0VNXKP) Executing workflow step 1 of 1
2022-12-27 13:45:23 [info] [Fn04GPR89U2F] (Trace=Tr04GM0VNXKP) Function execution started for app function 'Datastore demo'
creation result: {
  "ok": true,
  "datastore": "tasks",
  "item": {
    "id": "1",
    "title": "Make a phone call to Jim"
  }
}
query result: {
  "ok": true,
  "datastore": "tasks",
  "items": [
    {
      "id": "1",
      "title": "Make a phone call to Jim"
    }
  ]
}
modification result: {
  "ok": true,
  "datastore": "tasks",
  "item": {
    "due": "Dec 18",
    "id": "1",
    "title": "Make a phone call to Jim"
  }
}
deletion result: {
  "ok": true
}
2022-12-27 13:45:25 [info] [Fn04GPR89U2F] (Trace=Tr04GM0VNXKP) Function execution completed for function 'Datastore demo'
2022-12-27 13:45:26 [info] [Wf04GPULB35Y] (Trace=Tr04GM0VNXKP) Execution completed for workflow step 'Datastore demo'
2022-12-27 13:45:26 [info] [Fn04GSCL7DQC] (Trace=Tr04GHB2TXKQ) Function execution completed for function 'Datastore Demo Workflow'
2022-12-27 13:45:26 [info] [Wf04GPULB35Y] (Trace=Tr04GM0VNXKP) Execution completed for workflow 'Datastore Demo Workflow'

끝입니다. 이 예제 앱은 더이상 필요하지 않음으로 slack uninstall 명령어를 사용하여 설치된 앱을 제거하겠습니다.

$ slack uninstall
? Choose a workspace Choose a local development workspace
? Choose a local development workspace  seratch (dev)  T03E94MJU
   nifty-capybara-954 (dev) A04G9B1SFAB

⚠️  Danger zone
   App (A04G9B1SFAB) will be permanently deleted
   App (A04G9B1SFAB) will be uninstalled from dev (T03E94MJU)
   All triggers, workflows, and functions will be deleted
   All datastores for this app will be deleted
   Once you delete this app, there is no going back

? Are you sure you want to uninstall? Yes

🏠 Workspace Uninstall
   Uninstalled the app "nifty-capybara-954" from workspace "Acme Corp"

📚 App Manifest
   Deleted the app manifest for "nifty-capybara-954" from workspace "Acme Corp"

🏘️  Installed Workspaces
   There are no workspaces with the app installed
$

Dev vs Prod Datastores

Slack deploy 명령어를 사용하여 앱을 배포할때, Slack 인프라에서는 Prod 앱을 위한 전용의 데이터스토어를 만듭니다. 따라서 dev 버전의 앱과 prod 버전의 앱 사이에는 공유되는 데이터가 없습니다. 달리 말하면 dev 버전의 앱에 어떤 데이터 오퍼레이션을 하더라도 Prod 버전의 앱 데이터에는 영향을 미치지 않습니다.

마찬가지로 CLI 명령어로 Prod 버전의 앱을 배포할 권할을 가지고 있다면, slack datastore 커맨드 사용시에 Prod 의 데이터스토어에 접근 할 수 있음으로, slack datastore 명령어 사용시 Prod 버전의 데이터에 영향을 주지 않도록 반드시 주의하시기 바랍니다.

Wrapping Up

이번 튜토리얼에서는 다음과 같은 내용을 배워보았습니다.

  • CLI 명령어를 사용하여 Slack 데이터스토어에 데이터를 저장하고 쿼리를 실행하는 방법
  • 펑션 코드를 사용하여 Slack 데이터스토어에 데이터를 저장하고 쿼리를 실행하는 방법
  • slack uninstall 명령어를 사용하여 불필요한 앱을 삭제하는 방법

이 프로젝트는 다음주소에서도 확인하실 수 있습니다. https://github.com/seratch/slack-next-generation-platform-tutorials/tree/main/05_Datastores

이번 튜토리얼도 즐거우셨으면 좋겠네요. 마찬가지로 피드백이나 코멘트가 있으시다면 트위터 (@seratch) 로 연락주시거나, 여기 남겨주세요.

Happy hacking with Slack’s next-generation platform 🚀

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다