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 🚀