기본은 스프링 부트 레퍼런스 문서(http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/)를 

기준으로 해서 이런저런 이야기를 풀어가는 식으로 쓰려고 하는데...

올해 중에 마칠 수 있을까??


이걸 쓰려면 놀 시간을 줄여야 하는데...

놀시간 줄이는 건 뭔가 아깝단 말이지. @_@);;


어느버전을 기준으로 할지부터 잡아야겠는데...

Spring Boot 1.4.0.RELEASE  를 기준으로 해야겠다.

  1. Favicon of http://blog.outsider.ne.lr BlogIcon Outsider 2016.08.10 21:56 신고

    그럴때는 업무시간을 줄이면 됩니다!!

일단 써보기 시작.




최근 팀 커뮤니케이션으로 많은 사람들의 사랑을 받고 있는 슬랙(Slack).

슬랙에서는 외부에서 슬랙채널에 메시지를 보낼 수 있는 WebHook API를 제공하고 있다. 웹훅은 슬랙으로 데이터를 보내는 Incoming WebHook 과 특정조건에 부합되었을 때 외부의 데이터를 가져오는 Outgoing WebHook 이 있다.

웹애플리케이션에서 슬랙채널로 메시지를 보내는 것은 Incoming WebHook을 이용하게 된다.

그러기 위해서는 우선 팀슬랙에 Incomming WebHook을 설정한다.

NOTE

Slack: Incoming WebHook 설정

작업을 진행하기에 앞서서 채널을 하나 개설한다. 그후 통합Integration 으로 이동하여 'incoming webhook' 을 검색하여 설치하고 채널을 지정한다. 필요하다면 아이콘을 변경하는 작업을 한다. 화면에 나오는 웹훅 URL 을 복사해둔다.


스프링부트 프로젝트 생성

스프링부트 프로젝트를 생성한다.


build.gradle
buildscript {
	ext {
		springBootVersion = '1.3.5.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'

jar {
	baseName = 'slack-incoming-webhook'
	version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.projectlombok:lombok:1.16.8')
	compile('org.springframework.boot:spring-boot-starter-web')
	compile('com.google.guava:guava:19.0')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

RestTemplate 빈 선언

WebConfiguration
package io.honeymon.springboot.slack.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class WebConfiguration {

	@Bean
	RestTemplate restTemplate() {
		return new RestTemplate();
	}
}

SlackNotifier 컴포넌트 생성

SlackNotifier
package io.honeymon.springboot.slack.integration;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.google.common.collect.Lists;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * Slack Notifier
 *
 * @author honeymon
 *
 */
@Slf4j
@Component
public class SlackNotifier {

	@Autowired
	private RestTemplate restTemplate;

	public enum SlackTarget {
		// TODO webHookUrl 은 자신의 슬랙 IncomingWebHookAPI로 변경하세요.
		CH_INCOMING("https://hooks.slack.com/services/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", "incoming");

		String webHookUrl;
		String channel;

		SlackTarget(String webHookUrl, String channel) {
			this.webHookUrl = webHookUrl;
			this.channel = channel;
		}
	}

	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@Builder
	public static class SlackMessageAttachement {
		private String color;
		private String pretext;
		private String title;
		private String title_link;
		private String text;
	}

	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@Builder
	public static class SlackMessage {
		private String text;
		private String channel;
		private List<SlackMessageAttachement> attachments;

		void addAttachment(SlackMessageAttachement attachement) {
			if (this.attachments == null) {
				this.attachments = Lists.newArrayList();
			}
			this.attachments.add(attachement);
		}
	}

	public boolean notify(SlackTarget target, SlackMessageAttachement message) {
		log.debug("Notify[target: {}, message: {}]", target, message);

		SlackMessage slackMessage = SlackMessage.builder().channel(target.channel)
				.attachments(Lists.newArrayList(message)).build();
		try {

			restTemplate.postForEntity(target.webHookUrl, slackMessage, String.class);
			return true;
		} catch (Exception e) {
			log.error("Occur Exception: {}", e);
			return false;
		}

	}
}

SlackController 생성

SlackSenderController
package io.honeymon.springboot.slack.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import io.honeymon.springboot.slack.integration.SlackNotifier;
import io.honeymon.springboot.slack.integration.SlackNotifier.SlackMessageAttachement;
import io.honeymon.springboot.slack.integration.SlackNotifier.SlackTarget;

@RestController
public class SlackSendController {
	@Autowired
	private SlackNotifier slackNotifier;

	@RequestMapping(value = "/", method = RequestMethod.POST)
	public ResponseEntity<Boolean> send(@RequestBody SlackMessageAttachement message) {  (1)
		return ResponseEntity.ok(slackNotifier.notify(SlackTarget.CH_INCOMING, message));
	}
}
  1. POST ` 방식으로 전송을 할 때 `@RequestBody 로 클래스를 정의하면 자동으로 매핑된다.

slack-incoming-webhook 실행

$ git clone https://github.com/ihoneymon/slack-incoming-webhook
$ cd slack-incoming-webhook
$ ./gradlew springboot

포스트맨을 이용한 실행 확인

실제 슬랙 화면


팀채널로 많이 사용하는 슬랙.

배포한 앱에서 중요한 사항(항상 상태를 체크해야하는 상황)에 대해서 슬랙 채널로 공지하도록 하는 기능을 간단하게 구현해봤다. @_@)> 생각보다 쉽다. 많이.

예제에서는 컨트롤러에서 요청을 받아서 처리하는 방식으로 구현했다.


1. PID란?

PID(Process Identifier)는 각 프로세스/스레드를 구분해주는 번호다.

— PID란?

2. 스프링부트에서 PID 생성하도록 하기

스프링부트는 실행되고 있는 애플리케이션의 pid 를 생성하도록 설정하는 것이 쉽다.

2.1. 설정

  • application.yml  spring.pid.file 속성을 정의

spring:
  pid:
    file: springboot-app.pid

spring.pid.file 속성을 정의하지 않으면 application.pid 라는 기본이름으로 생성한다.

  • @SpringBootApplication 이 붙은 Application 클래스 수정

    • 기존

public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}
  • 변경

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Application.class);
    app.addListeners(new ApplicationPidFileWriter());   // pid 를 작성하는 역할을 하는 클래스 선언
    app.run(args);
}

2.2. 확인

  1. 간단하게 실행

$ ./gradlew bootrun &

3. PID를 이용한 프로세스 죽이기

  1. 스프링부트 앱 실행

$ ./gradlew bootrun &
  1. 생성된 pid 파일 확인

$ ls
springboot-app.pid  bin  build  build.gradle  gradle  gradlew  gradlew.bat  src
$ cat application.pid
{pid}
  1. 프로세스 죽이기

kill -9 $(cat ./springboot-app.pid)
// 앱이 실행되면서 생성된

4. 정리

스프링부트 1.3.x 버전에 들어서면서 스프링부트리를 유닉스/리눅스 서비스로 등록할 수 있는 기능이 추가되었다. 여기서 사용하기 위해 추가된 기능이라고 보면 될듯 싶다. 애플리케이션이 실행되면 실행되는 프로세스ID를 발급받고 이 프로세스ID를 이용해서 애플리케이션을 죽일 수 있게 된다. 이는 컨테이너나 마이크로서비스 용 애플리케이션에 유용한 기술이라고 생각한다.


[스프링부트] 빌드시 깃 커밋버전 정보 포함시키기

지금 개발하고 있는 애플리케이션은 깃 플로우git flow 를 이용해서 출시하고 있다. 출시할 때는 출시release 기능을 이용해서 master 브랜치에 태그를 생성하고 이를 배포하는 형태로 개발하고 있다.

애플리케이션을 사용하고 있는 필드가 늘어나고 있는데, 이 필드에 배포된 변경이력만으로는 어디까지 개발된 애플리케이션인지 알 수가 없다. 그러다가 스프링부트에서 Git commit information 를 빌드파일이 포함시킬 수 있는 부분을 확인한다. 빌드되는 시점에 커밋정보git.properties 에 저장하여 함께 배포하는 기능이다.

이 기능을 사용하려면,

JDK8 에서 빌드되어야 한다.


gradle-git-properties 사용절차

gradle-git-properties 플러그인 추가

  • 참고: com.gorylenko.gradle-git-properties

    Produce git.properties for spring-boot-actuator

  • build.gradle 수정

    buildscript {
        ext {
          springBootVersion = '1.3.2.RELEASE'
        }
        repositories {
          mavenCentral()
          maven {
                url "https://plugins.gradle.org/m2/"  //gradle 플러그인 URL
              }
        }
        dependencies {
          classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
          classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:1.4.11" // gradle-git-properties
        }
    }
     
    apply plugin: "com.gorylenko.gradle-git-properties"


빌드!


빌드 후 결과

  • 빌드된 배포파일 압축을 해제해보면 다음과 같은 구조로 되어 있다. gradle build 생성물 압축해제 그림

    • git.properties
      #
      #Fri Feb 26 10:40:36 KST 2016
      git.commit.id=bad6de66e0ecc9a2f2e2402fdac88f63d88a2305
      git.commit.time=1456448574
      git.commit.user.name=ihoneymon
      git.commit.id.abbrev=bad6de6
      git.branch=release/1.0.1.RELEASE
      git.commit.message.short=\#38 Code cleaning
      git.commit.user.email=ihoneymon@gmail.com
      git.commit.message.full=\#38 Code cleaning\n


액츄에이터 기능 중 info 가 활성화 되어 있다면!!

다양한 버전의 애플리케이션 배포본이 존재할 때 어떤 버전으로 운영되고 있는지 확인하는데 요긴할 듯 하다.

+ Recent posts