Slack Block Kit 활용

Slack Block Kit을 활용해 배송팁 공유 기능을 개발한 경험을 공유합니다

컬리에서는 Slack API 이렇게도 활용해요

많은 IT기업과 스타트업에서 Slack을 기업용 메신저로 활용하고 있습니다.

컬리에 들어오기 전의 저는 딱딱한 이메일 대신 SNS를 이용하듯이 하고 싶은 말을 편하게 나눌 수 있는 용도로 Slack을 사용해왔습니다.

하지만, 컬리에서는 Slack을 대화를 나누는 것뿐만 아니라 Slack API를 이용하여 많은 정보를 서로 공유 하고 알려주는 용도로 사용하고 있었습니다.

PDE 휴가자 알리미

집에서 편하게 쉬고 있는 직원을 보호하기 위해 오늘 휴가자 정보를 알려주는 기능

휴가자 알리미

통합 개발팀 중요 이벤트

개발팀 열심히 일하는 건 알지만, 이런 이벤트가 있으니 잊지 말고 참고하라는 중요 이벤트를 알려주는 기능

중요 이벤트 알림

터미널 입고 스캔 오류 알림

어떤 작업자 분이 입고 스캔을 잘못 입력했다고 알려주는 기능

터미널 입고 스캔 알림

컬리에서는 위 예시를 제외하고도 많은 용도로 사용을 하고 있습니다.

이러한 알림은 Incoming Webhook API를 통한 Managing Messages를 이용하면 만들 수 있습니다. 이 기능을 이용하여 정말 많은 업무에 효율성을 높힐 수 있습니다. 많은 기업에서 Slack을 이용하고 있는 이유이기도 합니다.

Slack Message에 Action을 줄 순 없을까??

위에 설명된 기능들은 메시지를 통해 업무에 도움이 되는 정보를 알려주는 용도로 쓰였습니다. 그때 문득 "혹시 메시지를 통해 이벤트를 받을 수는 없을까?" 라는 생각이 들었습니다.

때마침 Slack을 통해 배송 팁 정보에 대한 승인을 받을 수 있는 기능 제안이 있었고, 어떻게 하면 가능할지 조사를 진행하였습니다.

그때 Slack API에 Block Kit이라는 기능이 있는 것을 알게 되었습니다.

Block Kit 활용

Slack API Block Kit에는 아래와 같은 설명이 있습니다.

Block Kit는 Slack 앱의 일관성 있는 컨트롤과 개발 유연성을 제공하기 위해 만들어진 UI 프레임워크로, 메시지나 그 외의 다양한 사용자 경험 구축을 위해 사용할 수 있습니다.

Block Kit is a UI framework for Slack apps that offers a balance of control and flexibility when building experiences in messages and other surfaces.

Slack UI Framework 는 다양한 형태로 메시지에 UI를 접목할 수 있도록 도와줍니다. Block Kit에는 Button, 다양한 Menu들, Date Picker 등 여러 종류가 있으며, 자세한 내용은 Block Kit - Interactivity에서 확인 바랍니다.

Block Kit Builder를 통한 Template 확인

그럼 Block Kit에서 제공하는 UI를 어떻게 디자인하고 이용할 수 있을까요?? 다행히도 Slack에서 Block Kit Builder를 제공하고 있으며, 여러 Template 중 자신이 원하는 형태를 찾아 변경하여 테스트해 볼 수 있습니다.

그중 승인과 관련이 있는 한 Template 을 가지고 왔습니다.

Block Kit Builder Template

Builder 화면은 아래와 같이 구성되어 보여 줍니다.

Block Kit Builder

  1. Block Kit UI Components
  2. Message Preview 환경 설정
  3. 다른 Template 을 선택할 수 있는 페이지로 이동
  4. 선택한 Template의 JSON 코드를 표시
  5. Message 결과 화면

JSON 형태로 된 Template 코드를 만들면 그에 맞게 Slack에서 메시지를 Block Kit 형태로 변경합니다. 여기까지 오면 몇 가지 의문이 생기게 됩니다.

  • 그럼 어떻게 Template 코드인 JSON 데이터를 Slack으로 보내지?
  • Button과 같은 Component들의 이벤트는 어떻게 받을 수가 있지?

이에 대한 해답을 알기 전에 먼저 Slack App을 만들어서 설정하겠습니다.

Slack App에 Action을 설정해보자

Slack App을 새로 만들어서 Incoming Webhook과 Interactivity & Shortcuts(Action)를 정의할 예정입니다. 간단하게 역할을 설명하면 아래와 같습니다.

  • Incoming Webhook : Webhook URL을 통해 Slack으로 메시지를 전송
  • Interactivity : Request URL을 설정해서 Action이 일어났을 때, 특정 API로 전달
  • Shortcuts : Action을 정의하여 이벤트 확인

그럼 이제 하나씩 설정해 보겠습니다.

Slack App 생성

Slack Api Apps에서 우측 상단에 Your Apps라는 버튼을 누르면 App을 설정할 수 있는 페이지로 이동합니다. 페이지 중간에 Create New App 버튼을 누르면 아래와 같은 팝업이 나옵니다.

여기에 App 이름과 이용하고자 하는 WorkSpace를 지정합니다.

Create Slack App

Create App 버튼을 눌러 새로운 App을 만듭니다. 그럼 좌측에 아래와 같은 메뉴가 생성되며 좀 전에 말씀드린 Incoming Webhook 부터 설정해 보겠습니다.

Slack App Menus

Incoming Webhooks 설정

Incoming Webhooks 메뉴에 들어와서 활성화를 시켜 줍니다.

Incoming Webhook Activate

이제 원하는 Slack Channel에 Incoming Webhook을 생성 할 수 있습니다. Add New Webhook to Workspace 버튼을 눌러 설정하고자 하는 WorkSpace와 Channel을 선택하면 됩니다.

Create Incoming Webhook

전 delivery_tip_etc, delivery_tip_t 채널에 설정했습니다.

Incoming Webhook List

이제 이 Webhook Url을 통해 해당 채널로 Block Message를 보낼 예정입니다만, 아직 Slack에 메시지를 보낸 후 받을 Action을 설정하지 않았습니다.

Interactivity & Shortcuts 설정

Interactivity & Shortcuts 메뉴에 들어와서 활성화를 시켜 줍니다. Request URL에는 Action을 발생했을 시, 호출할 API 경로를 작성합니다. 여기서 정의된 Action들은 설정된 API를 호출하게 됩니다.

Interactivity Shortcuts Activate

Create New Shortcut 버튼을 눌러 Action 정보를 입력합니다.

  • Name : Action 명
  • Short Description : Action에 대한 간단한 설명
  • Callback ID : 어떠한 Action이 발생했는지 구분할 수 있는 ID

위 내용 중 가장 중요한 부분이 역시 Callback ID입니다. Slack에서 어떠한 사용자가 Action을 실행했는지 판단 할 수 있는 값으로 action_approveaction_reject 구분시켰습니다.

Create Interactivity Shortcuts

이렇게 해서 App설정은 모두 끝났습니다. 이제 어떻게 Action을 Spring Framework API에서 어떻게 받고 다시 재전송 하여 Slack 메시지를 변경할 수 있는지 알아 보겠습니다.

Spring Framework에 Slack 이벤트를 연동해보자

Spring Framework에서는 Slack에서 제공해 주는 SDK를 활용했습니다.

JAVA Slack SDK 종류

위 Github에 가보면 여러 Module이 존재하는데요. 저는 그중에서 3가지를 사용할 예정입니다.

  • slack-api-model : Slack으로 메시지를 주고받을 때 활용할 Model classes
  • slack-api-client : Slack으로 메시지를 보내는 데 활용
  • slack-api-backend : Slack에서 메시지를 받을 때 활용

자세한 사항은 Github에 있는 README.md 를 읽어보시면 됩니다.

그럼! 이제 Slack으로 메시지를 보내보겠습니다.

JAVA Slack SDK를 Dependency에 추가

pom.xml에 Dependency를 추가합니다. 위에서 말씀드린 SDK 외에 꼭 필요한 다른 Dependecy를 추가되어 있습니다.

<dependencies>

    ...

    <!-- start:: slack api -->
    <!-- https://mvnrepository.com/artifact/com.slack.api/slack-api-client -->
    <dependency>
        <groupId>com.slack.api</groupId>
        <artifactId>slack-api-client</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.slack.api/slack-app-backend -->
    <dependency>
        <groupId>com.slack.api</groupId>
        <artifactId>slack-app-backend</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.slack.api/slack-api-model -->
    <dependency>
        <groupId>com.slack.api</groupId>
        <artifactId>slack-api-model</artifactId>
        <version>1.0.0-RC2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
    <!-- If you use over 1.4.2 version, occur NoSuchMethodError copyInto -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    <!-- end:: slack api -->

    ...
</dependencies>

Slack SDK를 활용하여 Block Template 작성

Block Kit Builder에서 Template이 JSON 코드로 되어 있었습니다. Java SDK에서는 해당 Json Template를 LayoutBlock Interface로 정의했습니다.

아래에 있는 여러 블록은 모두 LayoutBlock을 상속받아 구현되어 있습니다.

Slack Blocks

Layout blocks에서 여러 종류의 Block들을 확인 할 수 있습니다. 이 중에 사용하고 싶은 Block을 List에 추가하여 Slack으로 전송해주면 끝입니다.

아래와 같이 Block을 만들어 보았습니다. 참고로 현재 Java 8을 활용 중이라 lambda 형식으로 구현하였습니다. (예제도 lambda로 되어 있다는 것은 비밀입니다. ㅎ)

List<LayoutBlock> layoutBlocks = new ArrayList<>();
// 텍스트를 남길 SectionBlock 입니다.
layoutBlocks.add(section(section -> section.text(markdownText("새로운 배송팁이 등록되었습니다."))));
// Action과 텍스트를 구분하기 위한 Divider 입니다.
layoutBlocks.add(divider());
// ActionBlock에 승인 버튼과 거부 버튼을 추가 하였습니다.
layoutBlocks.add(
  actions(actions -> actions
    .elements(asElements(
      button(b -> b.text(plainText(pt -> pt.emoji(true).text("승인")))
        .value(deliveryTip.getSeq().toString())
        .style("primary")
        .actionId("action_approve")
      ),
      button(b -> b.text(plainText(pt -> pt.emoji(true).text("거부")))
        .value(deliveryTip.getSeq().toString())
        .style("danger")
        .actionId("action_reject")
      )
    ))
  )
);

승인 버튼에는 action_approve, 거부 버튼에는 action_reject를 actionId로 정의하였습니다. 이건 눈치채신 분들도 있겠지만 Interactivity & Shortcuts에서 정의한 Callback ID입니다.

Block Template을 Slack으로 전송

이 Template으로 이제 메시지를 보내겠습니다. send 함수의 Parameter로 Slack Channel의 WebhookUrl과 Payload를 보냅니다. Payload에 위에서 만들었던 LayoutBlock List를 넣어주고, text 함수에는 만약 전송에 문제가 발생했을 시 보여주는 에러 메시지를 정의합니다.

Slack.getInstance().send(slackWebhookUrl, 
  payload(p -> p
    .text("슬랙에 메시지를 출력하지 못했습니다.")
    .blocks(layoutBlocks)
  )
);

그럼 아래와 같은 메시지가 출력이 됩니다!

Slack Blocks

  • 여기서 잠깐!!

지금까지 설명해 드린 Block Kit을 활용하여 Slack을 보내는 방법을 간단하게 나열해보겠습니다.

  1. Block Kit Builder에서 자신이 원하는 Template을 JSON 코드로 만들어서 미리 확인한다.
  2. Java Slack SDK를 dependency에 추가한다.
  3. 정의된 Template JSON 코드를 보면서 LayoutBlock List를 만든다.
  4. Incoming WebhookUrl과 함께 LayoutBlock List를 Payload에 넣어 보낸다.

이제 마지막으로 Action이 발생했을 때 API에서 받아 메시지를 재전송하는 것까지 해보겠습니다.

Slack에서 발생한 이벤트를 Spring API에서 응답

Slack Block 메시지에서 승인 버튼을 눌렀을 때 ActionBlock에서 Loading Icon이 생기면서 Interactivity에서 정의했던 API를 호출합니다.

Slack에서 API로 응답을 줄 때, ContentType 이 application/x-www-form-urlencoded;charset=UTF-8입니다. JSON으로 정의되어 있지 않기 때문에 RequestBody로 바로 받게 될 경우 MissingServletRequestParameterException 이 발생하게 됩니다. 여러 방법을 찾아보았지만, 제가 성공한 방법은 아래와 같이 String으로 JSON String을 먼저 받은 후 Gson을 이용하여 응답 객체로 변경하는 겁니다. (다른 방법을 알고 계시는 분 공유 부탁드립니다. ㅠㅠ)

Slack의 응답 객체는 BlockActionPayload 입니다. BlockActionPayload에 Template 정보가 그대로 넘어오기 때문에, 해당 정보를 수정해도 되고 아니면 새로운 Template를 만들어 보내도 됩니다.


@RequestMapping(value = "/deliverytip/approve", method = RequestMethod.POST,
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public Response approve(@RequestParam String payload) {
  // Json String -> BlockActionPayload 변경
  BlockActionPayload blockActionPayload = 
    GsonFactory.createSnakeCase()
      .fromJson(payload, BlockActionPayload.class);

  ...
}

응답받은 Slack Template 정보 변경

BlockActionPayload에 들어 있는 Template 정보를 변경해보겠습니다.

아래 코드는 상단 메시지를 변경 승인 여부에 따라 변경합니다.


// Block 수정 
blockActionPayload.getMessage().getBlocks().remove(0);
blockActionPayload.getActions().forEach(action -> {
Integer seq = Integer.parseInt(action.getValue());

if (action.getActionId().equals("action_reject")) {
  blockActionPayload.getMessage().getBlocks().add(0, 
    section(section ->
        section.text(markdownText("배송팁 등록을 *거부* 하였습니다."))
    )
  );
  dAppDeliveryTipService.updateDeliveryTip(seq, "N", userName);
} else {
  blockActionPayload.getMessage().getBlocks().add(0,
    section(section ->
        section.text(markdownText("배송팁 등록을 *승인* 하였습니다."))
    )
  );
  dAppDeliveryTipService.updateDeliveryTip(seq, "Y", userName);
}
});

새로운 LayoutBlock 리스트를 만들지 않고 첫 번째 Block을 제거하고 새로 추가시켰습니다. Block 수정을 완료했으면 ActionResponse 객체에 재전송할 LayoutBlock List를 넣습니다.

이때 replaceOriginal 함수는 아래와 같이 메시지를 변경 시켜 줍니다.

  • true : 표시되어 있는 Slack Message(Blocks)을 교체해버립니다. 즉, 기존 메시지는 삭제가 됩니다.
  • false : 표시되어 있는 Slack Message 다음으로 메시지가 출력이 됩니다.
// 재전송할 응답 객체 생성
ActionResponse response = 
  ActionResponse.builder()
    .replaceOriginal(true)
    .blocks(blockActionPayload.getMessage().getBlocks())
    .build();

Slack으로 수정된 Block Template 재전송

마지막으로 다시 Slack으로 전송하면 됩니다. 이때는 Action에 대한 전송이므로 ActionResponseSender를 이용합니다.

이때 다시 보낼 WebhookUrl은 요청받은 BlockActionPayload에 있는 ResponseUrl로 설정합니다.

Slack slack = Slack.getInstance();
ActionResponseSender sender = new ActionResponseSender(slack);
sender.send(blockActionPayload.getResponseUrl(), response);

이렇게 하면 아래와 같이 메시지가 변경되는 것을 확인 할 수 있습니다.

Slack Blocks

후기 : Slack Block Kit을 활용해보세요

저는 메신저를 그저 서로에 대한 생각을 공유하는 도구로만 생각하고 사용해왔습니다.

하지만, Slack Block Kit을 조사하면서 다양한 업무 현장에서 Slack을 효율적으로 활용할 수 있겠다는 생각이 들었습니다. Slack이 단지 업무 메시지를 주고받는 도구가 아니길 바라며, Slack Block Kit을 통해 다양한 이벤트를 주고받으며 더 효율적이고 재미있는 업무 생활이 되었으면 좋겠습니다.

(예를 들어 사내 이벤트성 퀴즈를 통한 컬리 상품 당첨!! ㅎ)

읽어 주셔서 감사합니다.





잠깐! 마켓컬리에서 함께 발전해가실 Java 서버 개발자(배송 시스템)분을 모십니다.

이 외에도 많은 분을 채용(컬리 채용 정보)하고 있으니 놀러 오세요.