중학생 1, 2학년 무렵이었나, 지금은 구글, 네이버, 다음으로 어느 정도 검색 사이트가 자리 잡은 듯하지만 야후, 라이코스, 국내에도 네이버, 미스다찾니, 코리아닷컴, 심마니 등 여러 검색 사이트/포털들이 있었던 것 같다.
그 때 쯤의 라이코스에서는 개인 계정별로 BBS라는 게시판 서비스를 제공하고 있었다. 싸이월드가 나오기 전이었는데 그 게시판을 마치 싸이월드처럼 썼다.
게시판은 HTML, CSS, 자바스크립트를 편집할 수 있는 기능을 제공했는데, 게시판을 좀 바꿔보고 싶어서 시내 서점에서 HTML 태그 사전을 사서 읽으며 내 BBS에 적용하곤 했다. 책 구성은 마치 요즘의 API Documentation처럼 태그 하나하나마다 설명을 해주는 식이었다.
이 책과 함께 윈도우 도움말 파일로 누군가가 만들었던 HTML 사전들도 있었고 그런 도움말 파일도 많이 봤던 것 같다.
요즘의 SNS처럼 친구를 맺는다거나 팔로우를 하는 기능은 당연히 없었는데, 그 때는 어떻게 다른 사람들의 BBS를 찾아다니고 글을 남겼는지 모르겠다. 다른 사람들의 BBS에 들어가서 디자인이 예쁘거나 신기한 자바스크립트 기능이 있으면 소스코드를 보고 내 BBS에도 적용하곤 했는데 뭔가가 마우스 커서를 따라다니는 걸 만드는 게 유행이었던 적이 있었다. 아날로그 시계도 마우스를 따라다니고, 반짝 반짝 가루나 별 같은 게 커서 뒤를 줄지어 따라다니는 그런 식이다.
자바스크립트는 그 때 처음 접했는데 프로그래밍 개념이 없던 그 때에는 "변수"같은 용어조차도 무슨 의미인지 어떻게 써야할지 몰라서 그저 자바스크립트스쿨넷인가 하는 사이트에서 코드를 그대로 베껴와서 BBS에 적용했다.
문득 생각이 나서 알라딘에서 검색했는데 처음으로 웹을 접하고 처음으로 본 IT 서적이라 내게는 추억이 깃든 책이다.
App Name에 슬랙 앱의 이름을 입력하고 Development Slack Workspace는 개발 시 연동할 워크스페이스를 선택해 준 후 [Create App] 버튼을 눌러 앱을 생성해 줍니다.
앱을 생성해 준 후 왼쪽의 OAuth & Permissions 페이지로 이동합니다.
OAuth & Permissions 페이지에서 스크롤을 아래로 내리면 Scopes 섹션이 있고, 이 중 Bot Token Scopes에서 [Add an OAuth Scope]를 클릭해서 chat:write와 channels:history 스코프를 추가해 줍니다.
chat:write는 Web API로 메시지를 보내기 위한 스코프이고, channels:history는 Events API로 채널의 메시지를 이벤트로 받기 위한 스코프입니다. 이 두 스코프를 추가해 준 후 스크롤을 위로 올려 [Install App to Workspace] 버튼을 클릭하면 아래와 같이 앱을 설치할 때 필요한 권한을 확인하는 페이지가 나타납니다. [Allow]를 클릭해서 앱을 워크스페이스에 설치해 줍니다.
[Allow] 버튼을 클릭하면 OAuth & Permissions 설정 페이지로 돌아오는데, Bot User OAuth Access Token이 생성되어 있는 것을 확인할 수 있습니다. 이 토큰은 봇을 이용해 슬랙 API를 사용할 때 사용하게 될 토큰입니다.
2. Heroku 가입, CLI 설치, Node.js 환경 세팅
Heroku는 PaaS(Platform as a Service) 중 하나로써 애플리케이션 빌드, 배포, 운영 환경을 제공해줍니다. 뒤에서 Node.js로 개발한 슬랙봇 애플리케이션을 Heroku에 배포하여 Slack API 서버와 통신하도록 합니다.
# package.json에 정의된 dependency 설치
npm i
npm i @slack/web-api
index.js 파일을 열어보면 express 모듈을 이용해 작성된 서버 코드가 있습니다. 선호하는 웹 프레임워크를 사용해도 되고, 여기에서는 템플릿의 코드를 그대로 활용하고 라우팅만 추가해 주도록 하겠습니다. index.js 파일을 아래와 같이 수정해 줍니다.
const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5000
const { WebClient } = require('@slack/web-api');
// OAuth & Permissions 설정 페이지에서 생성된 Bot User OAuth Access Token
const token = 'xoxb으로 시작하는 BOT_USER_OAUTH_ACCESS_TOKEN 값으로 이 부분을 바꿔주세요.';
const web = new WebClient(token);
express()
.use(express.static(path.join(__dirname, 'public')))
.use(express.json())
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'ejs')
.get('/', (req, res) => res.render('pages/index'))
.post('/slack/events', (req, res) => {
let body = req.body;
let event = body.event;
if(body.type === 'event_callback') {
if(event.type === 'message') {
// 메시지 이벤트인 경우, 메시지가 '안녕'이면 '안녕하세요' 메시지 전송
if(event.text === '안녕') {
console.log(`인사 메시지 수신 channel:${event.channel}, user:${event.user}`);
web.chat.postMessage({
channel: event.channel,
text: '안녕하세요.'
}).then(result => {
console.log('Message sent: ' + result.ts)
});
res.sendStatus(200);
}
}
} else if(body.type === 'url_verification') {
// URL 검증을 위한 처리
console.log('url verification')
res.send(body.challenge);
} else {
res.sendStatus(200);
}
})
.listen(PORT, () => console.log(`Listening on ${ PORT }`))
코드 수정이 완료되면 아래의 명령을 실행하여 Heroku 앱을 생성하고 커밋, 푸시해줍니다.
heroku create # Heroku 앱 생성
# 배포된 앱에 접속할 수 있는 URL이 출력됩니다.
# create 시 앱 이름을 지정해줄 수도 있습니다.
# Git Remote가 heroku라는 이름으로 추가됩니다.
git add .
git commit -m "슬랙 이벤트 연동 기능 추가"
git push heroku master # heroku origin으로 push하면 앱이 배포됩니다
Enable Events 오른쪽의 스위치를 토글해서 On으로 바꿔준 후 Request URL 에는 heroku create 명령을 실행했을 때 출력된 URL을 붙여넣어주고 path는 express에서 설정해준 /slack/events 를 붙여줍니다.
제 경우에는 https://warm-sierra-40409.herokuapp.com/slack/events 를 설정해주었습니다.
올바른 URL을 입력했다면 Request URL 레이블 오른편에 Verified가 표시됩니다. 그리고 슬랙에서 발생하는 이벤트 중 사용자가 메시지를 포스트한 경우를 Node.js 앱으로 받기 위해 [Subscribe to events on behalf of users]에서 [Add Workspace Event]를 클릭해 준 후 message.channels를 찾아 등록해 줍니다.
끝으로 페이지 하단의 [Save Changes]를 눌러 변경 사항을 저장해 줍니다. 앱의 permission scopes가 변경되었다고 앱을 재설치하라는 메시지가 나타납니다.
메시지의 reinstall your app 링크를 클릭해서 앱을 재설치 해줍니다.
5. 슬랙에서 확인
필요한 설정과 배포가 완료되어 슬랙과 슬랙봇 앱이 연동되어 있는 상태입니다. 이를 확인하기 위해 슬랙 앱이나 웹에서 연동한 워크스페이스로 접속한 후 채널에 앱을 추가하고 메시지를 보내보겠습니다.
채널에 접속한 후 [Add an app]을 클릭해서 표시되는 앱 중 In your workspace에 표시되어 있는 실습으로 만든 앱을 추가해 줍니다.
해당 채널에서 '안녕' 메시지를 입력하면 봇이 '안녕하세요'라고 메시지를 보내는 것을 확인할 수 있습니다.
node-schedule은 Node.js에서 동작하는 스케줄링을 위한 모듈입니다. 원하는 시간에 작업을 수행할 수 있는 기능을 제공하고 유사한 모듈로는 node-cron이 있습니다. 자바스크립트의 setInterval 함수나 setTimeout 함수로도 동일한 기능을 구현할 수 있지만 그런 작업을 더 편하게 할 수 있도록 도와줍니다.
const schedule = require('node-schedule');
// 매일 10시 정각에 '스크럼 시간입니다.' 출력
let job = schedule.scheduleJob('0 10 * * *', () => {
console.log('스크럼 시간입니다.')
});
3. 날짜 기반 스케줄링 Date 객체를 전달해서 스케줄링 할 수 있습니다. Date 객체에 설정된 시간에 1회 작업을 수행합니다.
const schedule = require('node-schedule');
// 2020년 1월 1일 0시 0분 0초 - 월은 0부터 시작
const date = new Date(2020, 0, 1, 0, 0, 0);
let job = schedule.scheduleJob(date, () => {
console.log('새해가 밝았습니다.');
});
4. Recurrence Rule 스케줄링(반복 작업) cron 표현식으로도 동일하게 반복 스케줄링 작업을 할 수 있지만 Recurrence Rule로 명시적으로 표현할 수 있습니다.
const schedule = require('node-schedule');
let recurrenceRule = new schedule.RecurrenceRule();
// Range 객체로 범위 설정
recurrenceRule.second = new schedule.Range(0, 59);
// 숫자 리터럴로 지정된 값 설정
recurrenceRule.minute = 20;
// 매시 20분 매초에 메시지 출력
schedule.scheduleJob(recurrenceRule, () => {
console.log('Hello!')
});
5. 객체 리터럴 Recurrence Rule 대신 객체 리터럴로 전달할 수도 있습니다. 객체 리터럴로 전달 시 프로퍼티로는 second, minute, hour, date, month, year, dayOfWeek, start, end, rule이 있습니다.
const schedule = require('node-schedule');
// 매 0초 마다 'hi' 출력
schedule.scheduleJob({
second: 0
}, () => {
console.log('hi')
});
const schedule = require('node-schedule');
// startTime, endTime, rule 조합
let start = new Date(Date.now() + 5000); // 5초 뒤
let end = new Date(startTiime.getTime() + 5000); // 10초 뒤
// start ~ end 동안 매초 마다 'hi' 출력
schedule.scheduleJob({
start,
end,
rule: '*/1 * * * * *'
}, () => {
console.log('hi');
});
프로젝트에서 Spring Cloud OpenFeign을 사용하고 있는데, 연동하고 있는 서비스에서 종종 오류가 나는 경우가 있어 급한 대로 예외 처리만 해두었으나, 장기적으로 안정적인 장애 예방을 위해 Resilience4j를 적용하려고 문서와 예제들을 찾아보고 있다.
Netflix Hystrix를 먼저 생각해두고 있었으나, 2018년 11월부로 개발이 중단되고, maintenance mode라고 하여, Resilience4j를 사용하기로 결정. (Google Trend 상으로는 아직 Hystrix를 더 많이 찾고 있는 듯)
Resilience4j는 공식 문서에서 "fault tolerance library for Java™"라고 소개하고 있고 Netflix Hystrix에서 영감을 받아 만들었으며, Java 8, 함수형 프로그래밍에 맞게 설계되었다고 한다.
Resilience4j의 코어 모듈인 CircuitBreaker, Bulkhead, RateLimiter, Retry, TimeLimiter, Cache와 라이브러리나 프레임워크와 연동할 수 있는 애드 온 모듈들이 있다.
당장은 서킷 브레이커만 사용하면 돼서 resilience4j-spring-boot2 디펜던시를 추가한 후 아래와 같이 작성하고 동작할 지 테스트를 해보았다.
@FeignClient(name = "api")
interface ApiClient {
@CircuitBreaker(name = "api", fallbackMethod = "fallback")
@GetMapping("/users")
fun getUsers(): List<User>
// Exception을 받을 파라미터가 필요.
// getUsers와 동일한 시그니쳐로 할 경우 NoSuchMethodException 발생
fun fallback(e: Exception): List<User> = emptyList()
}
우선 위 코드는 오류와 함께 애플리케이션이 실행되지 않는다. @FeignClient가 적용되어 프록시를 생성할 때 fallback 메서드에 매핑 정보가 없기 때문이다. 애너테이션이 없으면 그냥 무시할 줄 알았는데, Feign은 인터페이스에 정의된 메서드에 대해 모두 처리한다. Retrofit은 다른가 싶어 테스트 해봤으나 Retrofit도 이에 대해서는 동일하다. @Ignore 따위의 애너테이션이 있을까 싶어 찾아봤지만 없었고, GitHub Issue에 사용자들이 올린 글을 찾다보니 Feign, Retrofit 모두 default 메서드에 대한 처리에 어려움이 있어 아직은 지원하지 않는 기능이라고 한다.
fallback 메서드에 @GetMapping("/ignore")와 같이 붙여주면 애플리케이션은 실행되지만 getUsers()를 호출하여 오류가 발생했을 때 폴백 메서드를 호출하긴 하지만 프록시된 fallback을 호출하기 때문에 결과적으로는 GET /ignore를 요청하게 되어 원하는대로의 폴백으로 동작하진 않는다.
resilience4j-feign도 있고, resilience4-spring-boot2도 있지만 Spring Cloud Open Feign에 resilience를 위의 코드처럼 작성하는 건 괜히 더 복잡해져서 돌아가더라도 쓸 수 있는 형태로 적용해 보기로 했다. 아래의 두 가지 방법으로 시도했다.
1. Spring Cloud Open Feign으로 만들어진 빈의 래퍼 컴포넌트 작성(resilience4j-spring-boot2)
2. Spring Cloud Open Feign을 사용하지 않고 resilience4j-feign을 통해 Feign 빈 생성
혼자서 어설픈 영어 실력으로 169페이지에 달하는 RFC 문서를 얼마나 번역할 수 있을지는 의문이지만(...) iCalendar의 개념과 프로젝트에서 사용할 Event 타입에 대해 파악하고자 한글로 정리해봅니다.
1. 소개
지난 10년간 캘린더와 일정의 사용이 상당히 증가했습니다. 엔터프라이즈 및 엔터프라이즈간 비즈니스는 이 정보 기술을 사용하여 이벤트 및 작업을 신속하게 스케줄링하는데 의존하게 되었습니다. 이 RFC는 서로 다른 일정 관리 애플리케이션과 일정 관리 애플리케이션 간에 가능한 상호 운용성 수준을 향상시키기 위한 것입니다. 이 RFC 문서는 전자 캘린더 및 일정 정보를 교환하기 위한 MIME 콘텐츠 유형을 정의합니다. 인터넷 일정 및 예약 핵심 객체 사양 또는 iCalendar를 사용하면 일정 및 일정 관리 애플리케이션에 일반적으로 저장된 정보를 캡처하고 교환할 수 있습니다. PIM(개인 정보 관리자, Personal Information Manager) 또는 그룹 일정 관리 제품과 같은 다른 시스템에서 사용할 수 있습니다.
iCalendar 포맷은 애플리케이션이나 시스템간의 교환 형식으로 적합합니다. 형식은 MIME 컨텐츠 타입으로 정의됩니다. 이렇게 하면 SMTP, HTTP, 파일시스템, 메모리 기반 클립보드 사용 또는 드래그/드랍, 점대점 비동기 통신과 같은 데스크탑 대화형 프로토콜을 비롯한 여러 전송 매체를 사용하여 개체를 교환할 수 있습니다. 통신, 유선 네트워크 전송 또는 적외선과 같은 유선 전송의 일부 형태로 제공됩니다.
이 RFC 문서는 모임이나 약속, 할 일, 업무 일지 항목 요청, 회신, 수정 및 취소와 같은 일정 및 일적 작업을 지원하기 위해 이 컨텐츠 타입을 메시지 집합에 매핑하는 iCalendar 개체 메서드의 정의도 제공합니다. iCalendar 개체 메서드를 사용하여 약속 있음 / 없음 시간 데이터 요청 및 응답과 같은 다른 일정 및 예약 작업을 정의할 수 있습니다. 이러한 스케줄링 프로토콜은 [2446bis]에 정의된 iCalendar iTIP(iCalendar Transport-Independent Interoperability) 에서 정의됩니다.
이 RFC 문서에는 [RFC5234]에 정의된 인터넷 ABNF를 기반으로 하는 컨텐트 타입에 대한 공식 문법도 포함됩니다. 이 ABNF는 파서를 구현 시 필요하며 메모의 서술적인 구문 정의를 해석할 때 모호함이나 질문이 생길 때 최종 참고 자료로 사용됩니다. ABNF 구문으로 쉽게 표현할 수 없는 추가 제한 사항은 ABNF에서 주석으로 지정됩니다. 표준 구문에 대한 주석은 그렇게 취급됩니다.
The use of calendaring and scheduling has grown considerably in the last decade. Enterprise and inter-enterprise business has become dependent on rapid scheduling of events and actions using this information technology. This memo is intended to progress the level of interoperability possible between dissimilar calendaring and scheduling applications. This memo defines a MIME content type for exchanging electronic calendaring and scheduling information. The Internet Calendaring and Scheduling Core Object Specification, or iCalendar, allows for the capture and exchange of information normally stored within a calendaring and scheduling application; such as a Personal Information Manager (PIM) or a Group-Scheduling product.
The iCalendar format is suitable as an exchange format between applications or systems. The format is defined in terms of a MIME content type. This will enable the object to be exchanged using several transports, including but not limited to SMTP, HTTP, a file system, desktop interactive protocols such as the use of a memory- based clipboard or drag/drop interactions, point-to-point asynchronous communication, wired-network transport, or some form of unwired transport such as infrared.
The memo also provides for the definition of iCalendar object methods that will map this content type to a set of messages for supporting calendaring and scheduling operations such as requesting, replying to, modifying, and canceling meetings or appointments, to-dos, and journal entries. The iCalendar object methods can be used to define other calendaring and scheduling operations such as requesting for and replying with free/busy time data. Such a scheduling protocol is defined in the iCalendar Transport-independent Interoperability Protocol (iTIP) defined in [2446bis].
The memo also includes a formal grammar for the content type based on the Internet ABNF defined in [RFC5234]. This ABNF is required for the implementation of parsers and to serve as the definitive reference when ambiguities or questions arise in interpreting the descriptive prose definition of the memo. Additional restrictions that could not easily be expressed with the ABNF syntax are specified as comments in the ABNF. Comments with normative statements should be treated as such.
2. 기본 문법과 컨벤션
이 문서에서 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" 그리고 "OPTIONAL" 키워드는 [RFC2119]에서 설명하고 있는대로 해석되어야 합니다.
이 RFC 문서는 캘린더 및 일정 포맷을 정의하기 위해 설명적인 산문과 공식적인 표기법 모두 사용합니다.
이 RFC 문서에 사용된 모든 숫자 값은 십진수 표기법으로 제공됩니다.
모든 속성 이름, 속성 매개변수, 열거 속성 값 및 속성 매개 변수 값은 대/소문자를 구분하지 않습니다. 그러나 달리 명시하지 않는 한 다른 모든 속성 값은 대/소문자를 구분합니다.
참고: 모든 들여쓰기 된 편집 노트는 독자에게 추가적인 정보를 제공하기 위한 것입니다. 이 정보는 이 RFC를 준수하는 구현을 구축하는데 필수적이지 않습니다. 이 정보는 RFC의 특정 특징이나 특성을 강조하기 위해 제공됩니다.
iCalendar 객체의 형식은 [RFC2425] text/directory 미디어 타입의 구문을 기반으로 합니다. iCalendar 객체는 text/directory 미디어 타입 [RFC2425]의 프로파일은 아니지만 [RFC2425] 명세의 여러 요소를 재사용합니다.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119].
This memo makes use of both a descriptive prose and a more formal notation for defining the calendaring and scheduling format.
The notation used in this memo is the ABNF notation of [RFC5234]. Readers intending on implementing the format defined in this memo should be familiar with this notation in order to properly interpret the specifications of this memo.
All numeric values used in this memo are given in decimal notation.
All names of properties, property parameters, enumerated property values, and property parameter values are case-insensitive. However, all other property values are case-sensitive, unless otherwise stated.
Note: All indented editorial notes, such as this one, are intended to provide the reader with additional information. The information is not essential to the building of an implementation conformant with this memo. The information is provided to highlight a particular feature or characteristic of the memo.
The format for the iCalendar object is based on the syntax of the text/directory media type [RFC2425]. While the iCalendar object is not a profile of the text/directory media type [RFC2425], it does reuse a number of the elements from the [RFC2425] specification.
2.1. 포맷팅 컨벤션
이 문서에 정의된 요소는 설명에 정의되어 있습니다. 이들을 설명하는데 사용된 많은 용오는 이 문서의 표준 사용법과 다른 일반적인 사용법을 가집니다. 이 문서에서 일정 및 일정 모델, 핵심 개체(Core object) 또는 상호 운용성 프로토콜 [2446bis]의 요소를 참조하기 위해 일부 포맷팅 컨벤션이 사용되었습니다. 캘린더 및 스케줄 롤은 각 단어의 첫문자가 대문자인 텍스트의 인용 문자열로 참조됩니다. 예를 들어 "Organizer"는 [2446bis]에 의해 정의된 스케줄링 프로토콜 내에서 "Calendar User"의 롤을 나타냅니다. 이 문서에 정의된 캘린더 구성 요소는 대문자로 된 따옴표로 묶은 텍스트 문자열로 참조됩니다. 모든 캘린더 구성 요소는 문자 "V"로 시작합니다. 예를 들어, "VEVENT"는 이벤트 일정 구성 요소를 나타내고 "VTODO"는 수행할 일정(TO DO) 구성 요소를 나타내고 "VJOURNAL"은 일일 업무 캘린더 구성요소를 나타냅니다. iTIP [2446bis]에 의해 정의된 스케줄링 방법은 대문자로 된 따옴표로 감싸진 텍스트 문자열로 참조됩니다. 예를 들어 "REQUEST"는 스케줄링 캘린더 구성 요소를 생성 또는 수정하도록 요청하는 방법을 나타내며 "REPLY"는 요청 수신자가 일정 구성 요소의 "Organizer"로 상태를 업데이트 하는데 사용하는 방법을 나타냅니다.
이 문서에 정의된 속성은 대문자로 된 따옴표로 감싼 텍스트 문자열과 "property"라는 단어로 참조됩니다. 예를 들어, "ATTENDEE" 속성은 캘린더 사용자의 주소를 전달하는데 사용되는 iCalendar 속성을 참조합니다. 이 문서에 정의된 속성 파라미터는 소문자로 묶인 텍스트 문자열과 "parameter"라는 단어로 참조됩니다. 예를 들어, "value" 특성 값의 기본값 유형을 대체하는데 사용되는 iCalendar 특성 파라미터를 나타냅니다. 이 노트에 정의된 열거형 값은 대문자로 된 텍스트를 단독으로 사용하거나, "value"라는 단어를 사용하여 참조됩니다. 예를 들어 "MINUTELY" 값은 "RECUR" 값 유형의 "FREQ" 구성 요소와 함께 사용되어 1분 이상의 간격을 기반으로 반복 구성 요소를 지정합니다.
다음 표에는 이 문서에서 참조하는 [US-ASCII] 문자 집합의 다른 문자가 나열되어 있습니다. 각 문제에 대해 이 표는 US-ASCII 10 진수 코드포인트와 함께 이 문서에서 사용된 문자 이름을 지정합니다.
+------------------------+-------------------+
| Character name | Decimal codepoint |
+------------------------+-------------------+
| HTAB | 9 |
| LF | 10 |
| CR | 13 |
| DQUOTE | 22 |
| SPACE | 32 |
| PLUS SIGN | 43 |
| COMMA | 44 |
| HYPHEN-MINUS | 45 |
| PERIOD | 46 |
| SOLIDUS | 47 |
| COLON | 58 |
| SEMICOLON | 59 |
| LATIN CAPITAL LETTER N | 78 |
| LATIN CAPITAL LETTER T | 84 |
| LATIN CAPITAL LETTER X | 88 |
| LATIN CAPITAL LETTER Z | 90 |
| BACKSLASH | 92 |
| LATIN SMALL LETTER N | 110 |
+------------------------+-------------------+
2.1. Formating Convention
The elements defined in this memo are defined in prose.
Many of the terms used to describe these have common usage that is different than the standards usage of this memo.
In order to reference, within this memo, elements of the calendaring and scheduling model, core object (this memo), or interoperability protocol [2446bis] some formatting conventions have been used. Calendaring and scheduling roles are referred to in quoted-strings of text with the first character of each word in uppercase. For example, "Organizer" refers to a role of a "Calendar User" within the scheduling protocol defined by [2446bis]. Calendar components defined by this memo are referred to with capitalized, quoted-strings of text. All calendar components start with the letter "V". For example, "VEVENT" refers to the event calendar component, "VTODO" refers to the to-do calendar component, and "VJOURNAL" refers to the daily journal calendar component. Scheduling methods defined by iTIP [2446bis] are referred to with capitalized, quoted-strings of text. For example, "REQUEST" refers to the method for requesting a scheduling calendar component be created or modified, and
"REPLY" refers to the method a recipient of a request uses to update their status with the "Organizer" of the calendar component.
The properties defined by this memo are referred to with capitalized, quoted-strings of text, followed by the word "property". For example, "ATTENDEE" property refers to the iCalendar property used to convey the calendar address of a calendar user. Property parameters defined by this memo are referred to with lowercase, quoted-strings of text, followed by the word "parameter". For example, "value" parameter refers to the iCalendar property parameter used to override the default value type for a property value. Enumerated values defined by this memo are referred to with capitalized text, either alone or followed by the word "value". For example, the "MINUTELY" value can be used with the "FREQ" component of the "RECUR" value type to specify repeating components based on an interval of one minute or more.
The following table lists the different characters from the [US-ASCII] character set that is referenced in this document. For each character, the table specifies the character name used throughout this document, along with its US-ASCII decimal codepoint.
2.2. 관련 메모
이 RFC를 구현하는 사람은 이 문서와 함께 인터넷 캘린더 및 스케줄링 표준을 위한 프레임워크는 구성하는 몇 가지 다른 문서에 대해 잘 알고 있어야 합니다. 이 문서는 객체, 값의 타입, 속성과 속성 매개변수의 핵심 명세를 지정합니다.
- iTIP [2446bis]는 서로 다른 구현 간의 스케줄링을 위한 상호 운용성 프로토콜을 지정합니다.
- iCalendar 메시지 기반 상호 운용성 프로토콜(iMIP) [2447bis]는 [2446bis]에 대한 인터넷 전자 메일 바인딩을 지정합니다.
이 문서는 다른 문서에서 개념이나 정의의 명세를 반복하려고 시도하지 않습니다. 가능한 경우 이러한 개념이나 정의의 명세를 제공하는 문서를 참조하십시오.
3. iCalendar 객체 명세
다음 섹션에서는 캘린더 및 스케줄링 코어 객체 명세에 대한 세부 정보를 정의합니다. Calendaring, Scheduling Core Object 는 캘린더링과 스케줄링 정보의 컬렉션입니다. 일반적으로 이 정보는 하나 이상의 iCalendar 객체가 있는 iCalendar 스트림으로 구성됩니다. iCalendar 객체의 본문은 일련의 캘린더 속성과 하나 이상의 캘린더 구성 요소로 구성됩니다.
3.1. 절에서는 content 라인의 포맷을 정의합니다.
3.2. 절에서는 속성 파라미터 형식을 정의합니다.
3.3. 절에서는 속성 값의 데이터 타입을 정의합니다.
3.4. 절에서는 iCalendar 객체 포맷을 정의합니다.
3.5. 절에서는 iCalendar 속성 타입을 정의합니다.
3.6. 절에서는 캘린더 구성 요소 타입을 정의합니다.
3.7. 절에서는 캘린더 속성을 정의합니다.
3.8. 절에서는 캘린더 구성 요소 등록 정보를 정의합니다.
이 정보는 MIME 컨텐츠 타입 등록을 위한 필수 요소입니다. 또한 이 정보는 그러한 컨텐츠 등록과 독립적으로 사용될 수 있습니다. 특히 이 문서는 파일, 메모리 또는 네트워크 기반 전송 메커니즘에서 캘린더 및 스케줄 교환 포맷을 위해 직접 적용할 수 있습니다.
2.2. Related Memos
Implementers will need to be familiar with several other memos that, along with this memo, form a framework for Internet calendaring and scheduling standards. This memo specifies a core specification of objects, value types, properties, and property parameters.
o iTIP [2446bis] specifies an interoperability protocol for scheduling between different implementations;
o iCalendar Message-Based Interoperability Protocol (iMIP) [2447bis] specifies an Internet email binding for [2446bis].
This memo does not attempt to repeat the specification of concepts or definitions from these other memos. Where possible, references are made to the memo that provides for the specification of these concepts or definitions.
3. iCalendar Object Specification
The following sections define the details of a Calendaring and Scheduling Core Object Specification. The Calendaring and Scheduling Core Object is a collection of calendaring and scheduling information. Typically, this information will consist of an iCalendar stream with one or more iCalendar objects. The body of the iCalendar object consists of a sequence of calendar properties and one or more calendar components.
Section 3.1 defines the content line format; Section 3.2 defines the property parameter format; Section 3.3 defines the data types for property values; Section 3.4 defines the iCalendar object format; Section 3.5 defines the iCalendar property format; Section 3.6 defines the calendar component format; Section 3.7 defines calendar properties; and Section 3.8 defines calendar component properties.
This information is intended to be an integral part of the MIME content type registration. In addition, this information can be used independent of such content registration. In particular, this memo has direct applicability for use as a calendaring and scheduling exchange format in file-, memory-, or network-based transport mechanisms.
3.1. Content Lines
The iCalendar object is organized into individual lines of text, called content lines. Content lines are delimited by a line break, which is a CRLF sequence (CR character followed by LF character).
Lines of text SHOULD NOT be longer than 75 octets, excluding the line break. Long content lines SHOULD be split into a multiple line representations using a line "folding" technique. That is, a long line can be split between any two characters by inserting a CRLF immediately followed by a single linear white-space character (i.e., SPACE or HTAB). Any sequence of CRLF followed immediately by a single linear white-space character is ignored (i.e., removed) when processing the content type.
For example, the line:
DESCRIPTION:This is a long description that exists on a long line.
Can be represented as:
DESCRIPTION:This is a lo
ng description
that exists on a long line.
The process of moving from this folded multiple-line representation to its single-line representation is called "unfolding". Unfolding is accomplished by removing the CRLF and the linear white-space character that immediately follows.
When parsing a content line, folded lines MUST first be unfolded according to the unfolding procedure described above.
Note: It is possible for very simple implementations to generate improperly folded lines in the middle of a UTF-8 multi-octet sequence. For this reason, implementations need to unfold lines in such a way to properly restore the original sequence.
The content information associated with an iCalendar object is formatted using a syntax similar to that defined by [RFC2425]. That is, the content information consists of CRLF-separated content lines.
The following notation defines the lines of content in an iCalendar object:
contentline = name *(";" param ) ":" value CRLF
; This ABNF is just a general definition for an initial parsing
; of the content line into its property name, parameter list,
; and value string
; When parsing a content line, folded lines MUST first
; be unfolded according to the unfolding procedure
; described above. When generating a content line, lines
; longer than 75 octets SHOULD be folded according to
; Any character except CONTROL, DQUOTE, ";", ":", ","
VALUE-CHAR = WSP / %x21-7E / NON-US-ASCII
; Any textual character
NON-US-ASCII = UTF8-2 / UTF8-3 / UTF8-4
; UTF8-2, UTF8-3, and UTF8-4 are defined in [RFC3629]
CONTROL = %x00-08 / %x0A-1F / %x7F
; All the controls except HTAB
The property value component of a content line has a format that is property specific. Refer to the section describing each property for a definition of this format.
All names of properties, property parameters, enumerated property values and property parameter values are case-insensitive. However, all other property values are case-sensitive, unless otherwise stated.
시간의 틀에 갇혀 지내는 느낌이다. 뭔가 숨쉬기에도 벅찬.. 공간에 비유해보자면 박스 안에 갇혀있단 느낌을 받는 것 같다. 이직을 준비하는 동안에는 시간의 여유는 있었어도 마음과 경제적 여유가 없었고, 지금은 경제적인 속박에선 조금 헤어나왔어도 시간의 여유가 없어져버렸다. 거기에 더해 스트레스를 풀 수 있는 방법이 전혀 없다는 것도 이 답답함에 한 몫을 하고 있는 것 같다. 학교를 다닐 땐 점심 때마다 코인 노래방에가서 시원하게 소릴 지르다오거나, PC방에 가서 서든을 하거나, 혹은 겨울엔 사람들을 모아서 스노우보드를 타러 가는 개인적인 즐길거리가 있었는데 지금은 너무나 척박하다. 하다못해 집 근처나 회사 근처에 코인 노래방이라도 있었음 좋겠다 ㅜㅜ
너무 OutOfMemoryError에 대해서만 대비를 했던 탓인지 카운터를 맞았다. 답변도 OutOfMemoryError에 대한 해결 방법으로 해버렸다. 그러자 면접관께서 "그건 Heap 메모리에서구요, StackOverflow는 어떻게 될까요?"하고 내가 잘못 이해했다고 생각을 하셨는지 다시금 바로 잡아주셨지만.. 머리가 하얘졌더랬다.
면접이 끝나고, 정신이 좀 돌아오니 그제야 StackOverflowError는 호출 스택의 깊이가 너무 깊어질 때 발생한다는 게 생각났다. 웬만해선 질문을 받아도 알면 대답하고, 모르면 모른다고 대답을 했을텐데, 말그대로 정말 당황해서 어버버 했었던 것 같다.
먼저 Java API 문서의 StackOverflowError 클래스의 설명에는 "Thrown when a stack overflow occurs because an application recurses too deeply"로 되어있다. 그럼 StackOverflowError가 발생했을 때 어떻게 해야할까? Heap 영역과 마찬가지로 Stack 영역의 메모리를 늘려주면 되는걸까? 복구는 가능할까?
일단 에러 상황을 만들기 위해 무한 재귀호출을 하도록 아래와 같이 작성했다.
그리고 JVM Option에서 -Xss10K를 추가해서 실행해보았다. 근데 아래와 같은 메시지를 출력하면서 실행되지 않는다.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
The stack size specified is too small, Specify at least 160k
160K의 기준이 있나 싶어 1.8 버전의 JVM 명세를 찾아봤으나 딱히 160K에 대한 기준은 없은 것으로 봐서는 JVM 구현에서 결정하는 사항인 듯 하다. 검색을 하다보니 CASSANDRA 이슈에서 Stack 사이즈와 관련된 내용이 있었는데 Java 6의 경우는 -Xss128K도 되는데 Java 7부터는 160K의 하한이 생긴 모양이다. (https://issues.apache.org/jira/browse/CASSANDRA-4275)
다시 스택 크기를 일단 최소로 잡아서 160K로 조정해서 실행해보았다. 751까지 출력된 후 StackOverflowError를 발생시킨다. 일부러 재귀구조로 프로그램을 작성하지 않는 이상 이 정도의 깊이까지 호출하는 경우가 있을지는 의문이긴하다.
스택 크기를 320K로 조정했을 때는 2615까지, 480K일때는 4476, 640K일 때는 6345로 출력되었다. 메서드 구현에 따라 스택 메모리를 소모하는 정도는 달라질 수 있을 것이고, 또한 스택 크기를 지정할 때스레드 스택 프레임을 위해 기본으로 할당되는 영역까지 고려되어야 할 것 같다. Stack 메모리는 스레드별로 할당되므로 요청을 스레드별로 처리하는 웹 애플리케이션의 경우에는 Heap과 Stack의 크기가 세심하게 설정되어야 할 것 같지만 아직은 경험이 없으니 이에 대한 방법은 더 공부를 해봐야겠다.
그럼 StackOverflowError가 발생했을 때 일반적인 Exception과 마찬가지로 예외 처리가 가능할까? 일단 Error도 try-catch로 Error를 잡을 수는 있지만, 이미 비정상적인 조건에서 발생하게 되므로, 에러가 발생하기 전까지, 혹은 그 후에 처리가 정상적으로 되었는지 확신하기 어렵다. 이 경우에는 Error를 잡는 것보다, Error가 발생하는 원인을 찾아 해결하는 쪽이 더 바람직하다고 본다.
S멘토님께서 연락이 왔고, 근황 얘기를 하고 있다가 이직 준비를 하고 있다고 말씀드렸더니 '이력서랑 포트폴리오 좀 보내줘~ 내 메일 알지?'하셔서 보내드려야지 하고 깜빡하고 있다가 이틀 후에 다시 문자로 '이력서 보내줘~'하고 왔다. 회사가 궁금하기도 하고 멘토님도 오랜만에 뵐 겸 이따 사무실 가도 되냐고 물어봤더니 오라셔서 선릉으로 넘어갔다.
그때까진 그냥 멘토님 뵈러 가는 거였는데.. 사무실에 들어가 멘토님과 인사하고 잠깐 얘길 하다가... 갑자기 잠시만 기다려보라며 기술이사님을 모시고 오겠노라고 하셨다.
나: (...?? 기술이사님..??)
잠시 기다리니 멘토님과 기술이사님이 오셨고, 면접이 시작됐다. (?? 왜?)
나는 멘토님을 뵈러 간 것이었고.. 멘토님은 얘가 면접 보러 온다는 거구나 하고 생각하셨나보다.
이 때까지 기술 면접 준비를 하지 않은 상태이기도 했고, 무방비로 면접이 시작됐다.(눈물)
면접 땐 기술이사님이 대부분 질문을 하셨고, 그간의 경험이나 회사에서 서비스를 하고 있는 기술 위주의 질문이 이어졌고, 알고리즘, 자료구조(B+트리, 레드블랙 트리)에 대해서도 물어보셨다. 공부해야지 하고 있던 것들이지만 아직 하진 않은 상태라 당연히 대답은 하지 못했다. 모르는 부분에 대해 질문을 받다보니 아무래도 자신감 없는 태도로 대답한 것들도 있었다.
대부분의 기술 면접이 이력서나 포트폴리오를 기준으로 진행하는데, 포트폴리오에 있던 내용이 내가 직접 말한 것 말곤 언급되지 않은 것으로 봐서는 아마 사전에 포트폴리오는 멘토님만 보고, 기술이사님은 안보셨던 듯 하다.
회사 기술 스택이 나와는 내가 가진 기술 스택과 조금 차이가 있기도 했고, 여러모로 부족한 대답이 많아서 결과적으로는 그 자리에서 떨어졌다. 이력서와 포트폴리오를 보내긴 했지만... 고백하지도 않았는데 차인 느낌?
물론 갑작스럽게 진행된(나만 몰랐을지도 모르지만) 면접이긴 했지만 불편하거나 기분 나쁜 시간은 아니었다. 면접이 끝날 때 쯤에 기술이사님이 '솔직히 말씀드리자면, 확 와 닿는게 없다. 어필이 되는 부분이 있으면 좋았을텐데 그런 부분이 없었다."라고 말씀하시면서, '나중에 식사 한 번 같이 하면 좋겠네요' 라고 하면서 나가셨다.
그리고 멘토님과 얘기하면서 당연히 부족했을 면접에 대해 피드백을 받았는데, 알고 있던 것이나 경험이 있던 것들에 대해 대답할 때 그냥 사실을 있는대로 얘기하는 것 보단 좀 더 살을 붙여서 혹은 개인적인 생각이나 느낀 점, 배운 점들을 같이 답변하면 좋겠다는 내용이 주를 이뤘다. 확실히 나는 있는 사실대로 대답하는 경향이 있었고, 피드백을 받고 어떻게 고치면 좋을 지 방향을 잡을 수 있는 계기가 되었다.
예를 들어 면접관이 'A 프로젝트에는 어떤 기술을 사용했나요?'라고 질문을 했을 때, 예전 같았으면 질문에 대해 그대로 '스프링 프레임워크, 하이버네이트, 메시지 큐를 사용했습니다.' 라고 대답했을 것을 이젠 좀 더 살을 붙여서 '책으로만 학습하고 있던 것들을 실제 프로젝트에 적용하고 싶어서 스프링 프레임워크과 하이버네이트를 사용했고, API 요청에 대해 비동기적으로 처리하기 위해 메시지 큐를 사용했습니다. 책으로 봐서 알고 있다고 생각한 것들도 실제로 적용했을 때 어떤 어려움이 있는지 알게 되었고, 책을 벗어나 공부를 할 수 있는 곅기가 되었으며, 메시지 큐를 사용함으로써 콜백구조를 적용하여, 기존에 작업했던 동기적 프로세스를 갖는 프로젝트와 다른 아키텍쳐를 경험할 수 있었습니다.'라는 식으로 바뀌었다.
물론 모든 질문에 대해 장황하게 늘어놓는 것은 듣는 이로 하여금 안물안궁과 함께 지루한 상황이 될 수도 있지만, 특별히 강조하고 싶은 부분에 대해서는 사실 뿐만 아니라 이유나 배운 점, 느낀 점과 같이 개인적인 견해를 함께 전달하면 면접관의 마음을 살 수 있는 답변이 되지 않을까 싶다. 완급조절에 대한 판단은 상황을 보고 잘 얘기해야겠지만 말이다.
어쨌든 그 후 다른 회사 몇 군데에서 면접을 보게 되었고, 대답을 하면서도 스스로 피드백을 하게 되는 습관이 생겼고, 나 자신에게는 좋은 영향을 주고 있는 것 같다.