LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


LINE Trial Bot SDK의 개발에서 릴리스까지

들어가며

LINE 주식회사의 마쓰노입니다.

얼마 전 LINE BOT API Trial Account가 발표되어 전세계 개발자들의 열렬한 환영을 받았습니다. 기존에는 계약을 맺은 기업 개발자들만 개발이 가능했던 LINE의 Bot 계정을 개인도 쉽게 개발할 수 있게 되었기 때문입니다.

그러던 중에 이 자리를 빌어 LINE에서 왜 LINE BOT API Trial Account를 개발하게 되었는지, 또 1주일 만에 릴리스할 수 있었던 비결은 무엇인지, 전체적인 과정을 소개하고자 합니다.

Bot SDK를 제작하게 된 계기

LINE BOT API Trial Account를 공개하고 얼마 지나지 않아 LINE 내부에서도 많은 엔지니어들이 개발을 시작했습니다. 그런데, 그 엔지니어들은 이 API를 사용해서 Bot을 만들기는 힘들 것 같다!는 사실을 깨닫게 되었습니다.

특히, Java 등의 정적 언어일 경우에는 JSON을 매핑하기 위한 클래스를 억지로 만들어야 합니다. LINE BOT API Trial Account는 API의 엔드 포인트도 많고, 주고 받는 메시지 타입도 많아서 텍스트 메시지·이미지·스티커·리치 메시지 등 여러 개의 클래스를 구현해야 합니다. LINE Developers에 나와 있는 대량의 테이블로부터 클래스를 생성하는 것은 극한의 고통이 따르는 작업이기 때문에, 이건 큰일이다!라고 생각했던 것이지요.

사내 채팅에서 이런 이야기를 하다 보니, 같은 생각을 가진 사람들이 여럿 있다는 사실을 알게 되었습니다. 그리고, 다양한 이야기를 나누던 중에 LINE BOT API Trial Account를 많은 사람들이 사용하게 하려면 SDK를 공개하는 수밖에 없겠다!는 결론에 이르렀습니다.

이렇게 되면 엔지니어들을 모아서 언어별로 SDK를 동시에 제작해야 했습니다. 주요 언어이면서 사내에서 우수한 엔지니어들을 소집할 수 있는 언어를 중심으로 개발하기로 논의한 후, 일단 당사 개발의 중심 언어인 Java와 Perl 개발을 우선으로 진행하기로 했습니다. Ruby와 PHP는 숙련된 엔지니어가 있었기 때문에 포함시키기로 했고, 이 외에도 정적 언어인 golang의 개발이 필요하다고 판단해 개발 진행을 결정했습니다.

일단 착수하고 나니 개발이 빠르게 진행되어, 약 1주일 만에 각 언어가 구현되었습니다.

SDK를 제작할 때 유의했던 사항은 아래 5가지입니다.

  • 각 언어의 API 메서드명를 통일했습니다.
    • 언어별로 인터페이스가 너무 다르면 유저가 불편함을 느끼게 되기 때문에 통일을 했습니다.
  • 각 언어에 정통한 엔지니어를 모아서 개발했습니다.
    • 각 언어 커뮤니티의 표준적인 방식에 따라 릴리스하는 것이 바람직하기 때문에, 각 언어의 모듈 저장소에 릴리스했던 경험이 있는 엔지니어가 개발을 담당하기로 했습니다.
  • 품질 보장을 위해 리뷰를 철저하게 진행했습니다.
    • 각 언어별로 lint를 설정하여, 좋은 품질을 유지할 수 있도록 했습니다.
  • 보안성 체크를 실시했습니다.
    • 사내 보안팀에 요청하여 보안적인 측면에서도 코드 검사를 실시했습니다.
  • 의존 모듈을 최소화했습니다.
    • 의존 모듈이 많아지면 학습 비용이 상승하기 때문입니다.
    • Ruby의 ActiveSupport처럼 로드하면 전역으로 내장 클래스에 영향을 주는 라이브러리는 이용하지 않았습니다.

이런 단계를 거쳐 LINE BOT API Trial Account가 릴리스되었습니다.

Trial Bot SDK for Java의 구성

그럼 제가 담당한 Java SDK의 구성에 대해 소개하겠습니다.

지원 대상 Java 버전은 8로 했습니다. LINE BOT API Trial Account는 체험판이라서 환경을 새로 구축하는 경우가 많을 것이라고 생각했습니다. Java의 경우 모듈을 세세하게 나누는 것이 일반적이기 때문에 세분화했습니다. 어느 정도의 상세 단위(granularity)로 구성할지가 고민이었지만, 무리 없는 수준의 상세 단위로 나눴습니다.

line-bot-model

HTTP에서 리퀘스트/응답 시에 JSON을 매핑하는 클래스입니다. JSON의 매핑에는 Jackson을 이용하는 것을 전제로 구현했습니다. Java 개발에서는 Jackson과 Gson이라는 2가지 JSON 라이브러리가 유명한데, 당사에서는 Jackson이 인기가 있어서 Jackson을 채택했습니다.
매핑하기 위한 Bean은 accessor를 IDE로 생성해도 되지만 lombok을 채택했습니다. Lombok을 이용하면 annotation을 배치하기만 해도 아래와 같이 Getter/Setter 등을 생성할 수 있습니다. Lombok을 이용해 매우 보기 편한 코드를 작성할 수 있습니다.

작성할 코드 생성되는 코드
public class User {
 @Getter @Setter
 private String name;
}
public class User {
  private String name;
  public void setName(String name) {
    this.name = name;
  }
  public String getName() {
    return this.name;
  }

Lombok으로 설정하는 Bean은 Jackson으로 매핑만 하는 용도라면 @Data라는 annotation을 추가하여 Getter/Setter/equals/hashcode 등을 한꺼번에 생성해 해결해도 됩니다. 실제로 당사에서 개발 중인 일반적인 웹 애플리케이션에서는 그렇게 하는 경우가 많이 있습니다.

하지만, SDK의 경우에는 이 객체 자체를 Immutable로 해 두어야 '이 필드에 setter가 있다는 건 어떤 의미가 있는 것인지도 몰라...' 등의 쓸데없는 의문이 생기지 않고, 더 이해하기 쉬워집니다.

그래서 객체는 Immutable로 하고 setter 메서드를 구현하지 않기로 했습니다.

Jackson에서 Immutable인 클래스를 이용할 경우, 생성자의 인수에 @JsonProperty라는 annotation을 부여하여 Jackson이 생성자를 이용해서 객체를 생성할 수 있게 해야 합니다. 구체적으로는 아래와 같이 하면 됩니다.

@Getter
@ToString
public class EventResponse {
    private final Integer version;
    private final Long timestamp;
    private final String messageId;
    private final int statusCode;
    private final String statusMessage;
    private final List<String> failed;
 
    public EventResponse(
            @JsonProperty("version") Integer version,
            @JsonProperty("timestamp") Long timestamp,
            @JsonProperty("messageId") String messageId,
            @JsonProperty("statusCode") int statusCode,
            @JsonProperty("statusMessage") String statusMessage,
            @JsonProperty("failed") List<String> failed) {
        this.version = version;
        this.timestamp = timestamp;
        this.messageId = messageId;
        this.statusCode = statusCode;
        this.statusMessage = statusMessage;
        this.failed = failed;
    }
}

line-bot-api-client

HTTP API의 클라이언트 모듈이며, 이 SDK의 핵심이 되는 부분입니다. HTTP 클라이언트 라이브러리로는 Apache HttpClient를 사용했습니다. 커뮤니티에서도 이용자가 많고 안정적입니다.

하지만, HTTP 클라이언트 라이브러리 분야는 흥망성쇠의 연속입니다. 트렌드가 바뀔 가능성도 있기 때문에, 인터페이스를 정의하여 HTTP 클라이언트 라이브러리의 구현이 달라져도 대폭 수정을 가할 필요가 없도록 설계했습니다.

line-bot-servlet

LINE의 Bot 계정은 웹 애플리케이션으로 동작합니다. Java로 구현한 경우라서 웹 애플리케이션을 servlet으로 작성하는 것이 기본이기 때문에, servlet에서 쉽게 이용할 수 있도록 해야 합니다.

Servlet은 구식 API라고 야유를 받는 경우도 있지만, Servlet API는 Lightweight Language의 Rack/WSGI/PSGI 등에 해당됩니다. 때문에 Servlet API를 파악해두는 것은 중요합니다.

특정 프레임워크에 의존한 샘플 코드와 SDK 등을 준비한다고 하더라도 그 SDK를 잘 모르는 사람에게는 생소하기 때문에, Servlet을 사용한 예시를 포함시킬 필요가 있습니다.

line-bot-spring-boot

Servlet의 레이어에서 Bot 작성이 가능하니 충분하다고 할 수도 있겠지만, 세간에는 웹 애플리케이션 프레임워크 등을 이용하여 작성하기를 원하는 분들도 계실 것으로 판단해 spring boot로 간편하게 만들 수 있는 SDK도 준비해 보았습니다.

의존성을 설정하면 아래와 같이 작성하기만 해도 쉽게 echo 서버를 실행할 수 있습니다. 아주 간단합니다.

package com.example.bot.spring.echo;
 
import com.linecorp.bot.client.LineBotClient;
import com.linecorp.bot.client.exception.LineBotAPIException;
import com.linecorp.bot.model.callback.Message;
import com.linecorp.bot.model.content.Content;
import com.linecorp.bot.model.content.TextContent;
import com.linecorp.bot.spring.boot.annotation.LineBotMessages;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@SpringBootApplication
public class EchoApplication {
    public static void main(String[] args) {
        SpringApplication.run(EchoApplication.class, args);
    }
 
    @RestController
    public static class MyController {
        @Autowired
        private LineBotClient lineBotClient;
 
        @RequestMapping("/callback")
        public void callback(@LineBotMessages List<Message> messages) 
          throws LineBotAPIException {
            for (Message message : messages) {
                Content content = message.getContent();
                if (content instanceof TextContent) {
                    TextContent textContent = (TextContent) content;
                    lineBotClient.sendText(textContent.getFrom(),
                            textContent.getText());
                }
            }
        }
    }
}

sample-spring-boot-echo

Echo Bot의 샘플입니다. 단순하게 말을 걸면 그대로 따라 하는 Bot을 구현한 샘플입니다. Bot 형식 중 가장 간단한 형식입니다. 각 언어의 SDK에는 echo Bot의 샘플 코드가 각각 저장소에 포함되어 있습니다.

쉽게 Bot 구현이 가능하다는 점을 실감할 수 있도록, 작성해야 하는 분량을 최대한 줄였습니다(그리고 작성 분량을 줄일 수 있도록 SDK를 설계했습니다).

sample-spring-boot-kitchensink

Echo Bot처럼 심플한 샘플 코드도 중요하지만, 실제로 어떤 기능을 이용할 수 있는지를 총망라하여 확인할 수 있는 샘플 코드도 중요합니다. 이 모듈에서는 SDK의 모든 기능을 전부 이용하는 샘플을 작성했습니다. 구체적으로는 모든 이벤트 타입을 똑같이 따라하도록 구현하기만 하면 됩니다.

SDK 자체의 동작을 확인하기 위해서 모든 기능을 다 테스트할 수 있는 샘플 코드를 구현한 것이기도 합니다. 문서를 토대로 구현했다 하더라도 정상적으로 동작될지 여부는 실제 기기로 테스트해 보지 않으면 보장할 수 없는 법이니까요!

마치며

위와 같은 과정을 통해 LINE BOT API Trial Account가 개발되었습니다. 자세한 내용은 LINE의 GitHub을 참고해주세요.

LINE에서는 SDK 개발에 자발적으로 참여할 수 있는 엔지니어를 모집하고 있습니다. 많은 관심 부탁드립니다.

서버, 클라이언트, 게임 엔지니어 [LINE Messaging][Family App][LINE Pay][LINE Game]