카테고리 보관물: DEV

비즈니스 프로세스를 그리자. BPMN 2.0

소프트웨어 프로젝트 문서를 작성하면서 사용자가 어떻게 사용하고, 시스템은 어떻게 작동하는지 그림으로 나타내야 할때가 다반사이다.

사용자가 이 시점에서 무엇을 해야하고, 입력받은 시스템은 어떻게 동작해야 하는지 그림으로 그리려면 Flow Chart(1921년부터 사용, 1985년 ISO 표준 제정)와 UML을 많이 사용했을 것이다.

하지만 엔지니어링에 포커스를 둔 Flow Chart와 UML은 어딘가 모르게 3% 정도 부족할때가 생기게 마련이다.(혹시.. 나만 그런가?)

2,005년에 UML의 복잡성을 과감하게 단순화(Simplify)시킨 새로운 표준이 나왔으니 이름하여 BPMN(Business Process Modeling Notation) 1.0이다.

그리고 여러번의 판올림을 하고 나서 2011년에 BPMN 2.0이 제정되었다.

역사는 이쯤에서 하고..


왜 BPMN을 만든 것일까?

단적으로 액티비티 다이어그램이 가진 부족한 점을 보완하기 위함이다. UML은 비즈니스 프로세스보다 소프트웨어 개발 관점에서 바라본다. 그래서 비즈니스 프로세스를 표현하기에는 부족한 면이 있었다. 그래서 비즈니스 프로세스 관점에서 프로세스를 표현하는 접근방식을 취하고 있다.

BPMN의 구성

BPMN은 3가지로 구성되어 있다.

Basic

가장 기초적인 구성이다. 도형도 간단하고 기술자가 아닌 사람들도 10분 정도만 인지하고 바로 적용할 수 있을 정도로 쉽다.

Core

SW 엔지니어들이 보기 시작하는 단계이다. Basic에서 좀더 세분화된 구성이다. 시스템을 구현할때 필요로 하는 Activity에 대한 세부 분류가 표현 가능하고, 예외 처리도 표현 가능하다.

Advaned

실제 코드를 다이어그램으로 표현하는 수준이다. Core보다 더 SW 엔지니어링에 가깝도록 세분화 되어 있다. 이 수준은 BPM 시스템에 구현하면 실제로 동작하는 단계이다.

이 포스팅에서는 가장 많이 사용하는 Element를 기준으로 기술한다.

BPMN의 요소들(Elements of BPMN)

BPMN의 규칙은 아주 간단하다. 무조건 Start로 시작해서 End로 끝내야 한다. 액티비티간에 흐름이 끊어지면 안된다. 이것만 지키면 된다.

시작, 종료를 표기하자.

Flow Dimension(Start, Intermediate, End)

Intermediate는 시작과 종료 중간에 유입되는 이벤트를 나타낸다.

프로세스 액티비티(Activity)를 표기하자.

액티비티의 모양은 모서리가 둥근 사각형이다. 사각형 안에 프로세스명을 기입하면 된다.

Activity

이제는 서로를 이어줘야 할 시간! — Connections

서로를 이어줄때도 규칙과 모양이 다르다.

  • Sequence Flow

프로세스의 흐름을 나타낸다. Activity를 비롯한 Element들을 이어준다. 하지만 나중에 나올 Swimlane을 벗어나지 못한다는 제약사항이 있다. 실선에 색칠된 삼각형으로 표기한다.

Sequence Flow
  • Message Flow

Swimlane간에 전달하는 메시지를 의미한다. 메시지라함은 말(Speak), 시스템 등록같은 다른 사람이나 시스템에 지시와 요청을 전달하는 것을 포괄적으로 나타낸다. 점선에 빈 삼각형 화살표로 표기한다.

Message Flow
  • Association

액티비티와 관련된 산출물을 연결할때 사용한다. 산출물은 말(Speak), 제품, 데이터등 포괄적인 의미를 갖고 있다. 작은 점선으로 표기한다.

Association

지금까지 나온 Start, Activity와 End만으로도 많은 것을 표현할 수 있다.

Simple!!

“이럴때는 요거, 저럴때는 저거” 분기처리 — Gateway

상황에 따라서 판단해서 분기해야 하는 것은 어디서나 있기 마련이다. 분기없이 흘러가는 프로세스가 완벽하다고 생각한다. 하지만 그런게 과연 있기나 한걸까?

기호는 Flowchart와 동일하게 마름모이고, 표기법은 동일하다.

Gateway

Gateway Sample

여러 계층을 표현하자. — Pool, Swimlane, Blackbox Pool

우리가 비즈니스 프로세스를 표현할때 하나의 시스템이나 인력으로 처리하는 프로세스는 없다. 99.9%는 없다고 봐도 된다. 연동과 협업을 표현하는 방법은 Swimlane을 사용하자. 수영장에 레인을 생각하면 이해가 빠르다.

Pool, Swimlane

순서대로 살펴보면 첫번째 Vertical Swimlane은 하나의 계층만을 나타낸다. 두번째는 계층안에서 세부적으로 나눌때 레인을 여러개 두는 경우이고, 세번째 블랙박스는 나타내고자 하는 관심이 아닌 계층은 단순하게 블랙박스로 나타내는 것이다.

블랙박스를 나타내는 이유는 아키텍처의 기본인 “관심사의 분리 — Seperate of Concern”를 적용한 것이다.

위의 모든 것을 적용한 다이어그램으로 마치겠다.

마지막으로 주의할 점은 너무 세부적으로 그려야 겠다는 욕심을 버려야 한다는 것이다. 나타내고자하는 의도에 대해서 심사숙고해서 작성하자.

Business Process Modeling Notation 2.0 (BPMN)

여기에서는 BPMN 2.0 사양에서 제공하는 기본적인 업무  프로세스 모델링 요소에 대해서 살펴보기로 한다다음 컬럼에서 이들 BPMN 모델링 요소를 사용하여 어떻게 업무 프로세스를 모델링하는가에 대해 살펴보게  것이다.

Business process modeling, user, timer, task, service, none, end, message, horizontal pool, pool, exclusive gateway,

BPMN 2.0 개요

BPMN 2.0은 목적에 따라 다음과 같은 3가지 수준의 업무 프로세스 모델링을 지원한다.

 

·         서술 수준(descriptive level)

·         분석 수준(analytic level)

·         실행 수준(executable level)

 

서술 수준(descriptive level) 모델링은 업무 프로세스를 가시화 또는 시각화(visualization)하는 것을 목적으로 한다. 이것이 뭐 그리 대단하겠느냐고 생각할 지 모르겠지만 비즈니스 프로세스를 가시화한다는 것은 기본이면서도 아주 중요한 문제다사실 많은 기업이나 기관에서도 전사적인 업무  프로세스 모델을 보유하고 있는 경우는 드믈다대부분의 기업이나 기관에서 업무와 업무의 처리 흐름은 오직 담당자 또는 시스템을 개발한 개발자의 머리속에만 존재한다좀 더 나은 경우는 이것이 업무 매뉴얼에 서술되어 있는 경우이다그러나 이 또한 시간이 지나가면서 실제 업무 프로세스는 바뀌고 업무 매뉴얼과 일치하지 않는 경우가 발생하게 마련이다기업이나 기관에 표준이라고 할 수 있는 가시적으로 정의되어 있는 업무 프로세스가 없다보니 문제가 발생하거나 업무 프로세스가 변경될 때마다 사람의 기억에 의존하여 사안마다 다른 일관성 없는 해결 방안이 나올 수 밖에 없다이것이 누적되면 기업이나 기관의 업무 처리 방법은 복잡하며 일관성을 유지할 수 없게 되며이런 상황에서 합리적인 문제 해결 결론에 도달하기는 어렵게 된다따라서 기업이나 기관에 소속되어 있는 모든 사람들이 확실하게 이해할 수 있는 표준 업무 프로세스를 정의하고 이것을 유지 관리하는 일은 매우 중요한 일이 된다.

다음 단계의 분석 수준(analytic level) 모델링은 업무 효율성 제고를 위해 업무 프로세스를 모델링하는 것을 목적으로 한다. 이 수준에서의 업무 프로세스 모델링은 현행 프로세스(as-is process)를 정의하고현행 프로세스를 분석하여 개선할 수 있는 부분을 찾아 여러 개의 후보 프로세스(candidate process)를 모델링한 후에 이들 프로세스를 ABC(Activity Based Cost) 기반으로 시뮬레이션(simulation)을 수행하여 이들 중 최적의 프로세스를 찾아 최종 프로세스(to-be process)로 정의하는 일련의 과정들로 구성된다이 수준의 업무 프로세스 모델링은 현행 업무 프로세스를 개선하고자 할 때 필요하다.

마지막으로 실행 수준(executable level) 모델링은 BPM(Business Process Management) 기반으로 업무 프로세스를 실행하는 것을 목적으로 한다. 정의된 업무 프로세스 모델은 그대로  BPMS(Business Process Management Suite)가 제공하는 프로세스 실행 엔진(process execution engine)에 의하여 실행된다. BPMN 1.x까지 BPMN 모델은 BPEL(Business Process Execution Language)로 변환되어 BPEL 엔진에 의해 실행되었지만 BPMN 2.0부터는 에러가 많은 BPEL로의 변환 과정을 거지치 않아도 직접 프로세스 실행 엔진에 의해 BPMN 모델을 실행할 수 있게 되었다.

서술 수준 또는 분석 수준에서 프로세스 모델링을 수행하는 것도 의미있는 일이지만특히 실행 수준에서 프로세스를 모델링하고 그 프로세스 모델을 BPMS 상에서 실행하는 일은 매우 중요한 의미를 갖는다업무적으로는 업무 환경의 변화에 대하여 업무 담당자 스스로 민첩한 대응 능력을 갖게 된다는 것을 의미하며기술적으로는 프로세스 중심적인 시스템 구축으로 애플리케이션으로부터 프로세스와 업무 규칙을 분리함으로써 시스템의 변화 요구에 대하여 민첩한 대응 능력을 갖게 된다는 것이다. BPM 기반의 이점에 대해서는 다음에 상세히 설명하도록 한다.

각 프로세스 모델링 수준에 따라 업무 분석가나 프로세스 개발자가 사용하는 세부적인 BPMN 2.0 모델링 요소도 달라질 수 있다.  예를 들어 서술  수준에서는 업무 분석가가 업무 프로세스의 전반적인 흐름을 표현하는데 적당한 비교적 간단한 모델링 요소만을 사용하여 모델링할 수 있다그러나 분석 수준에서 업무 프로세스를 분석하고 시뮬레이션하기 위해서는 복잡한 모델링 요소들을 잘 활용하여 정확하게 업무 프로세스를 모델링해야 할 필요가 있다마찬가지로 실행 수준에서도 BPMS가 지원하고 추가로 제공하는 기능을 잘 활용하여 업무 프로세스를 상세하게 설계해야 한다.

여기에서는 업무 분석가가 서술 수준에서 업무 프로세스를 모델링하는데 필요한 BPMN 2.0 표기법에 대해서만 살펴보기로 한다.

 

BPMN 2.0 모델링 요소는 다음과 같이 분류된다.

 

·         플로우(flow)

·         참가자(participant)

·         데이터(data)

·         커넥터(connector)

·         아티팩트(artifact)

플로우(flow)

플로우는 업무 프로세스의 행위를 정의한다. BPMN 다음과 같은 3종류의 플로우 객체를 정의한다.

 

·         이벤트(event) : ‘어떤 일이 발생했다는 신호(signal)

·         활동(activity) : 프로세스에서 수행된 작업(work)

·         게이트웨이(gateway) : 라우팅 로직(routing logic)

이벤트(event)

이벤트는 프로세스 실행 과정에서 발생하여 정상적인 흐름에 영향을 주는 것을 말한다업무 프로세스는 항상 이벤트로 시작하여 이벤트로 끝난다시작 이벤트(start event)가 발생할 때 프로세스 실행이 시작되고,  프로세스 흐름이 끝나면 끝 이벤트(end event)가 발생한다시작 이벤트는 이벤트를 받지만 끝 이벤트는 이벤트를 발생시킨다다시말해 시작 이벤트는 이벤트 리스너(event listener)로서 이벤트 정보를 처리하여 특정한 행위를 계속 수행한다그러나 끝 이벤트는 이벤트 전송자(event transmitter)로서 이벤트 정보를 생성하여 전송한다

 

시작 이벤트는 다음과 같이 선이 가는 원형으로 표현된다.

 

 

[그림.1 시작 이벤트]

 

위의 표기법은 일반적인 시작 이벤트(non start event)를 표현한다이것은 시작 이벤트로서 이벤트가 어떻게 발생하는지를 명시하지 않는다어떤 방식으로 이벤트를 발생시키는 지를 명시하고 싶다면 원 안에 내부 심볼을 표현한다만약 메시지를 받을 때 시작 이벤트가 발생하는 메시지 시작 이벤트(message start event)는 다음과 같은 내부 심볼을 갖는다.

 

 

[그림.2 메시지 시작 이벤트]

 

특정 시간에 시작 이벤트를 발생시키는 타이머 시작 이벤트(timer start event)는 다음과 같은 내부 심볼을 갖는다.

 


[그림.3 타이머 시작 이벤트]

 

프로세스 흐름이 끝날 때 발생되는 끝 이벤트는 다음과 같이 선이 굵은 원형으로 표현된다.

 

 

[그림.4 끝 이벤트]

 

시작 이벤트와 마찬가지로 위 표기법은 일반적인 끝 이벤트(non end event)를 표현한다프로세스 흐름이 끝날 때 어떤 종류의 끝 이벤트가 발생시키는 지를 명시하지 않는다만약 프로세스 흐름이 끝날 때 메시지 이벤트를 발생시킨다면 메시지 끝 이벤트(message end event)를 사용한다메시지 끝 이벤트는 다음과 같은 내부 심볼을 갖는다.

 

 

[그림.5 메시지 끝 이벤트]

 

만약 실행되고 있는 모든 프로세스 흐름을 즉시 종료시키고 싶다면 다음과 같은 내부 심볼로 표현되는 종료 끝 이벤트(terminate end event)를 사용한다.

 

[그림.6 종료 끝 이벤트]

 

활동(activity)

활동(activity)이란 업무 프로세스 안에서 실행되는 작업을 나타내는 일반적인 용어로서 다음과 같은 3가지 유형의 활동이 있다.

 

·         작업(task)

·         서브 프로세스(sub process)

·         활동 호출(call activity)

 

작업(task)이란 완전히 수행되거나 아니면 전혀 수행되지 않는 원소적(atomic) 단위 활동을 말한다따라서 작업은 한 번에 한 장소에서 수행될 수 있는 일이어야 한다작업은 다음과 같이 모서리가 둥근 직사각형의 형태의 표기법을 갖는다내부에는 수행되는 작업을 표시하는 텍스트가 표시된다.

 

[그림.7 작업]

 

위의 표기법은 추상적인 작업(abstract task)을 표현한다추상적인 작업은 프로세스 실행에는 영향을 주지 않고 프로세스를 명확하게 이해하기 위해서 사용한다구체적인 작업의 내부 행위를 명시하고 싶다면 작업의 유형을 지정해야 한다만약 화면에서 정보를 입력하는 것과 같이 사용자가 수행하는 작업을 명시하고 싶다면 사용자 작업(user task)를 사용한다사용자 작업은 다음과 같은 내부 심볼을 갖는다.

 

[그림.8 사용자 작업]

 

또한 웹 서비스(web services)나 소프트웨어 솔루션과 같은 외부 서비스(external service)를 실행하는 작업을 수행한다면 서비스 작업(service task)를 사용한다서비스 작업은 다음과 같은 내부 심볼을 갖는다.

 

[그림.9 서비스 작업]

 

서브 프로세스(sub process)란 프로세스 안에서 내부의 세부 실행 흐름을 포함하는 활동으로서브 프로세스는 다음과 같은 유형을 갖는다.

 

·         포함(embedded)

·         재사용(reusable)

·         트랜잭션(transaction)

·         이벤트(event)

·         특별(ad-hoc)

 

여기에서는 이들 유형의 서브 프로세스 중에서 포함과 재사용 서브 프로세스에 대해서만 다루기로 한다.

 

포함 서브 프로세스(embedded sub process)  부모 프로세스(parent process) 내부에 포함되는 서브 프로세스를 말한다포함 서브 프로세스는 작업과 마찬가지로 모서리가 둥근 사각형으로 표현되며 펼쳐진 형태로 표현될 수 있고축소된 형태로 표현될 수도 있다펼쳐진 형태로 표현될 때는 – 내부 심볼과 함께 다음과 같이 표현된다.

 

 

 [그림 10.펼쳐진 서브 프로세스]

 

펼쳐진 형태로 포함 서브 프로세스가 부모 서브 프로세스 내부에 직접 표현된다면 세부 프로세스 흐름을 이해하기는 좋지만 아무래도 전체 프로세스 다이어그램을 복잡하게 만든다이 경우에는 다음과 같이 축소된 형태로 표현하여 다이어그램을 단순하게 만들 수 있다축소된 서브 프로세스에는 + 내부 심볼이 표현된다.

 

 

[그림 11.축소된 서브 프로세스]

 

재사용 서브 프로세스(reusable sub process)는 다른 여러 부모 프로세스에서 재사용할 수 있도록 정의된 프로세스를 말한다포함 서브 프로세스는 하나의 부모 프로세스 내부에 직접 포함되지만재사용 서브 프로세스는 부모 프로세스와는 별도의 다이어그램에 표현된다또한 포함 서브 프로세스가 단 하나의 부모 프로세스에만  종속되어 포함되지만재사용 서브 프로세스는 여러 부모 프로세스에서 재사용될 수 있다그러니까 재사용 서브 프로세스는 여러 부모 프로세스에 공통적인 활동의 흐름을 표현하는데 적당하다서브 프로세스는 다음과 같이 별도의 다이어그램으로 표현된다.

 

[그림 12.서브 프로세스]

 

부모 프로세스 다이어그램에서 이와 같은 재사용 서브 프로세스를 호출하여야 할 때는 활동 호출(call activity)를 사용한다재사용 서브 프로세스를 호출하는 활동 호출은 작업과는 달리 굵은 선으로 표현되며 축소된 서브 프로세스와 같이 + 내부 심볼을 갖는다.

 

[그림 13.활동 호출]

 

게이트웨이(gateway)

프로세스는 다양한 대체 흐름이나 동시 흐름을 포함할  있다게이트웨이(gateway) 이러한 프로세스 흐름을 분할하고 분할된 흐름을 다시 병합하는 일을 통제한다.  게이트웨이는 다음과 같이 마름모 꼴로 표현된다.

 

[그림 14.게이트웨이]

 

게이트웨이의 가장 일반적인 유형이 배타적(XOR) 게이트웨이이다배타적 게이트웨이는 조건에 따라 프로세스 흐름을 배타적으로 분할하고배타적으로 분할된 프로세스 흐름을 병합한다배타적 분할은 마치 프로그래밍 언어에서 if 문이나 switch 문을 사용하는 것과 같다조건에 따라 어느 하나의 흐름으로 분할되는 것이다배타적 병합은 배타적 분할로 분할된 프로세스 흐름을 병합하는 역할을 한다그림 14 게이트웨이는 배타적 게이트웨이를 표현한다배타적 게이트웨이는 기본적인 게이트웨이트인 셈이다배타적 게이트웨이는 다음과 같이 구체적인 내부 심볼을 가질  있다.

 

[그림 15.배타적 게이트웨이]

 

분할된 모든 프로세스 흐름이 동시에 흘러간다면 병렬 게이트웨이(parallel gateway) 사용한다배타적 게이트웨이가 분할된 흐름  하나만 흘러간다면병렬 게이트웨이는 분할된 모든 흐름이 동시에 흘러간다는 점에서 대조를 이룬다병렬 게이트웨이로 분할된 프로세스 흐름은 병렬 게이트웨이로 병합되어야 한다병렬 게이트웨이는 다음과 같은 내부 심볼을 갖는다.

 

[그림 16.병렬 게이트웨이]

참가자(participant)

참가자란 프로세스 안에 있는 활동을 실행하거나 실행할 책임을 갖는 업무 실체(business entity) 말한다참가자는 회사와 같은 조직(organizational unit) 수도 있고 어떤 일을 수행하는 역할(role) 수도 있다. BPMN에서 참가자는 (pool) 표현된다풀은 다음과 같이 라벨(label) 갖는 모서리가 각진 사각형으로 표현된다레이블에는 참가자의 이름이 표시된다.

 

  

[그림 17.]

 

풀은 일반적으로 위의 그림과 같이 수평으로 표시되지만 수직으로 표시될 수도 있다하나의 프로세스 다이어그램은 여러 풀을 포함할  있다만약 프로세스 다이어그램이 하나의 풀만을 포함한다면 일반적으로 풀은 표현하지 않는다.

 

비즈니스 프로세스는 하나의 조직 내의 여러 역할을 수행하는 참가자에 의해 실행될  있다이들 역할은 풀에 포함되는 레인(lane)으로 표현된다레인은  안에 포함되며 라벨을 갖는 직사격형으로 표현된다.

 

[그림 18.레인]

 

 안에 레인이 하나만 있다면 일반적으로 레인은 생략된다풀과 레인은 수영장을 생각하면 쉽게 이해할  있다하나의 수영장에는 여러 개의 풀이 있고하나의 풀에는 여러 레인이 있다그러나 수영장과는 달리 하나의 레인이 하위에 여러 레인을 포함할 수도 있다.

 

풀과 레인에는 다음과 같이 조직 또는 역할이 수행하는 프로세스의 활동 흐름이 표현된다.

 

 [그림 19.활동 흐름]

데이터(data)

데이터(data) 프로세스 흐름에서 처리되는 데이터 구조와 정보를 표현하며다음과 같은 4가지 데이터 모델 요소를 사용할  있다.

 

·         데이터 객체(data object)

·         입력 데이터(data input)

·         출력 데이터(data output)

·         데이터 저장소(data store)

 

데이터 객체(data object) 프로세스 흐름 안에서  처리되는 일반적인 데이터 항목을 표현하며 다음과 같이 표현된다.

 

[그림 20.데이터 객체]

 

프로세스 흐름을 실행하는 동안에 변경되는 데이터 객체의 상태는 다음 그림과 같이 대괄호([ ]) 안에 표시한다.

 

[그림 21.데이터 객체 상태]

 

활동을 수행하는데 필요한 데이터 객체라면 해당 데이터 객체를 입력 데이터(data input) 표현할  있다입력 데이터는 다음과 같은 내부 심볼을 갖는다.

 

[그림 22.입력 데이터]

 

활동을 수행한 결과 데이터 객체가 생성되었다면 해당 데이터 객체를 출력 데이터(data output)으로 표현할  있다출력 데이터는 다음과 같은 내부 심볼을 갖는다.

 

 

[그림 23.출력 데이터]

 

처리하는 데이터가 데이터베이스와 같은 정보 시스템에 있다면 다음과 같이 데이터 저장소(data store) 사용할  있다.

 

[그림 24.데이터 저장소]

커넥터(connector)

프로세스 모델은 프로세스 처리 흐름을 표현해야 하므로 플로우 객체들을 연결할  있는 연결 객체(connecting object) 필요하며, BPMN 2.0에서는 다음과 같은 4가지 연결 객체를 제공한다.

 

·         시퀀스 플로우(sequence flow)

·         메시지 플로우(message flow)

·         데이터 연관(data association)

·         연관(association)

 

시퀀스 플로우(sequence flow) 화살표를 사용하여 프로세스 다이어그램에서 플로우 객체의 실행 순서를 표시한다시퀀스 플로우는 다음과 같이 실선의 화살표로 표시된다.

 

[그림 25.시퀀스 플로우]

 

시퀀스 플로우는 같은 (pool) 안에 정의된 플로우 객체 사이의 실행 흐름에만 사용할  있다 사이에 또는서로 다른  안에 있는 플로우 객체 사이에는 메시지 플로우(message flow) 사용해야 한다.  메시지 플로우는 점선의   화살표로 표시된다.

 

[그림 26. 메시지 플로우]

 

메시지 플로우는 다음과 같이 풀을 연결하는데 사용할 수 있다.

 

[그림 27.풀 연결]

 

또는 다음과 같이 서로 다른 풀에 있는 플로우 객체를 연결하는데 사용된다.

 

[그림 28.풀 사이의 플로우 객체 연결]

 

메시지 플로우에 전달되는 정보를 포함하는 데이터 구조 즉메시지(message)를  추가할 수 있다상호작용의 초기에 전달되는 메시지 즉초기 메시지(initializing message)는 다음과 같이 표시된다.

 

[그림 29.초기 메시지]

 

상호작용에서 반환되는 메시지 즉비초기 메시지(non-initializing message)는 다음과 같이 표시된다.

 

[그림 30.반환(비초기메시지]

 

이들 메시지는 메시지 플로우에 부착되어 메시지 플로우를 통하여 어떤 메시지가 전달되는 지를 표시할 수 있다다음 그림은 풀을 연결하는 메시지 플로우에 메시지가 부착된 결과를 보여준다.

 


[그림 31.풀 연결  메시지 플로우에 메시지 부착]

 

다음 그림은서로 다른 풀에 있는 플로우 객체를 연결하는 메시지 플로우에 메시지가 부착된 결과를 보여준다.

 

[그림 32. 풀 사이의 플로우 객체 연결 메시지 플로우에 메시지 부착]

 

데이터 연관(data association)은 데이터 객체와 활동의 입출력 사이에 데이터를 이동시키는데 사용된다데이터 연관은 다음과 같이 점선 화살표로 표시된다.

 

[그림 33.데이터 연관]

 

다음 그림은 입력 데이터가 작업에 제공되는 것을 표현한다.

 

[그림 34.입력 데이터 연관]

 

다음 그림은 작업으로부터 출력 데이터가 생성되는 것을 표현한다.

 

[그림 35.출력 데이터 연관]

 

다음 그림은 데이터 객체가 두 작업 사이에 전달되는 것을 표현한다.

 


[그림 36.데이터 객체 연관]

 

다음 그림은 데이터 저장소를 통하여 두 작업 사이에 데이터가 이동하는 것을 표현한다.

 

[그림 37.데이터 저장소 연관]

 

연관(association)에 대해서는 아티팩트(artifact) 절에서 살펴보기로 한다.

아티팩트(artifact)

아티팩트(artifact)는 프로세스 실행에 영향을 미치지 않고 프로세스 다이어그램에만 표현하는 구문적인 정보를 말한다. BPMN 2.0은 다음 2가지 아키팩트를 제공한다.

 

·         주석(annotation)

·         그룹(group)

 

주석(annotation)은 텍스트로 표현된 설명이다주석은 다음과 같이 표현된다

 

[그림 38.주석]

 

주석은 모델 요소에 부착되며이때 주석과 모델 요소를 연결하는 커넥터를 연관(association)이라고 한다연관은 다음과 같이 점선으로 표현된다.

 

[그림 39.주석 연관]

 

그룹(group)은 일련의 모델 요소의 그룹에 어떤 특별한 의미를 부여하기 위해 사용되는 시각적인 표현이다그룹은 다음과 같은 모서리가 둥근 사각형 점선으로 표현된다.

 

[그림 40.그룹]

 

[Windows, Apache2.4] 날짜별로 로그찍는 방법.

{아파치가 설치된 경로}/httpd.conf 파일에서

log_config_module이 아래처럼 주석이 없는 것을 확인 후 (기본 설정이면 주석이 없다.)

LoadModule log_config_module modules/mod_log_config.so

<IfModule log_config_module> (안에서)

CustomLog “logs/access.log” common (이렇게 되어있는 것을)

CustomLog “|bin/rotatelogs.exe logs/%Y%m%d_access.log 86400” combined (이렇게 바꾼다.)

*. Windows 운영체제에선 반드시 rotatelogs 다음에 .exe 를 붙여주어야 한다.

MS SQLSERVER 저장프로시저에서 OUTPUT, RETURN TABLE 사용

1. 예제 테이블 만들기

테스트를 하기 위해서 테이블과 데이터를 생성한다.

————————————————————————————–

CREATE TABLE [dbo].[제품테이블](
[품번] [int] IDENTITY(1,1) NOT NULL,
[품명] [nvarchar](50) NULL,
[단가] [int] NULL,
CONSTRAINT [PK_titles] PRIMARY KEY CLUSTERED ([품번] ASC)
)

GO

INSERT INTO 제품테이블(품명, 단가) VALUES
(‘AA’, ‘1000’)
, (‘AB’, ‘2000’)
, (‘AC’, ‘3000’)
, (‘AD’, ‘4000’)
, (‘AE’, ‘5000’)
, (‘AF’, ‘6000’)
, (‘AG’, ‘7000’)
, (‘AH’, ‘8000’)
, (‘BA’, ‘1000’)
, (‘BB’, ‘2000’)
, (‘BC’, ‘3000’)
, (‘BD’, ‘4000’)
, (‘CE’, ‘5000’)
, (‘CF’, ‘6000’)
, (‘CG’, ‘7000’)
, (‘CH’, ‘8000’)

GO

————————————————————————————–

 

2. OUTPUT 파라미터와 RETURN 값이 있는 저장프로시저 만들기

————————————————————————————–

CREATE PROC usp_UpdatePrice
@rate  float = 0.01
,@product nvarchar(50)
,@ucount int OUTPUT               — 출력파라미터로 지정

AS
SET NOCOUNT ON

UPDATE 제품테이블

 SET 단가 = 단가 + (단가 * @rate)
WHERE 품명 LIKE @product

SET @ucount = @@ROWCOUNT

IF @@ERROR <> 0

    RETURN(-1)                           — 출력값으로 지정

​ ELSE
BEGIN

​       SELECT TOP 1 * FROM 제품테이블
       WHERE 품명 LIKE @product
ORDER BY 단가 DESC

       RETURN(0)— 출력값으로 지정


END

————————————————————————————–

참고로 RETURN(값) 에서 값에는 정수만 가능하다.

위와 같이 프로시저를 만들고  아래와 같이 프로시저를 호출하여 OUTPUT 파라미터와 RETURN 값을

확인해 보자

————————————————————————————–

DECLARE @cnt int
DECLARE @rtn int

EXEC @rtn = usp_UpdatePrice -0.1, ‘CE’, @cnt OUTPUT

SELECT @cnt AS 업데이트, @rtn AS 오류

————————————————————————————–

위와 같이 구문을 실행하면 아래와 같은 출력 값을 볼 수 있다.

위의 예제에서 처럼 출력되는 것을 확인할 수 있다.

참고로, 테이블 반환 매개변수는 OUTPUT의 매개변수로 사용할 수 없다.

 

3. 프로시저에서 반환하는 쿼리결과 테이블을 테이블변수에 저장하기

 

쿼리 결과로 테이블을 반환하는 프로시저를 먼저 생성한다.

————————————————————————————–

CREATE PROCEDURE TABLERETURN
AS
BEGIN
SELECT * FROM 제품테이블
END


 

1) 테이블변수에 저장하기

위 프로시저에서 출력되는 결과를 테이블 변수에 저장하기

————————————————————————————–

DECLARE @TResults TABLE (
PartID int
,PartName nvarchar(20)
,Price int
)

INSERT @TResults EXEC TABLERETURN

SELECT * FROM @TResults

————————————————————————————–

 

2) 임시테이블에 저장하기

위 프로시저에서 출력되는 결과를 테이블 변수에 저장하기

————————————————————————————–

CREATE TABLE #TResults (
PartID int
,PartName nvarchar(20)
,Price int
)

INSERT #TResults EXEC TABLERETURN

SELECT * FROM #TResults

DROP TABLE #TResults

————————————————————————————–

위 코드를 실행하면 아래와 같이 출력된다.

————————————————————————————–

** 원격 서버의 프로시저를 호출 해 테이블 반환을 받을 때 설정값

 

자바스크립트 예제로 살펴보는 JSONP의 기본원리

JSONP는 한 웹페이지에서 도메인이 다른 웹페이지로 데이터를 요청할 때 사용하는 자바스크립트 개발 방법론입니다.

기본적으로 웹 브라우저는 도메인이 다른 웹 페이지로는 Ajax 등의 방법으로 접근하지 못하게 제한하고 있는데, 이것을 동일출처원칙(Same-origin policy)이라고 합니다. 그러나 실무에서는 부득이 다른 도메인에 연결된 서버로 데이터를 요청해야만 하는 상황을 만나게 됩니다. JSONP는 바로 이러한 경우에 동일출처원칙을 회피하는 일종의 편법 입니다.

JSONP는 동일출처원칙을 회피하기 위해 <script> 요소를 이용합니다. 본래 자바스크립트에서는 Ajax를 비롯한 어떠한 방법으로도 직접 다른 도메인의 웹페이지로 데이터를 요청할 수 없습니다. 그러나 <script> 요소는 도메인이 다른 스크립트 파일이라 하더라도 임베드할 수 있기 때문에, 이 성질을 이용하는 것입니다.

국내 웹에서 JSONP에 대해 검색을 하면, jQuery를 사용하여 간편하게 JSONP를 구현한 방법을 소개하는 포스팅이 주류를 이루고 있습니다. 이 방법은 Ajax를 구현할 때와 유사한 방식으로 쉽고 간편하게 JSONP를 구현할 수 있게 해주지만, 한편으로는 JSONP의 기본원리에 대해서는 소홀하게 만드는 양면성을 가지고 있습니다.

금번 포스팅에서는 JSONP의 기본원리를 설명하기 위하여, 순수 자바스크립트로 JSONP를 구현한 예제코드를 소개합니다. 또한 Ajax와의 비교를 통해 JSONP의 효용과 한계에 대해서 짚어볼 것입니다. 마지막으로 실무에서 활용하기 좋은 jQuery에서의 JSONP 구현방법까지 살펴보고 나면 글을 마치게 될 것입니다.

 

예제코드 개요

회원명부에서 이름으로 검색하는 웹페이지를 작성하려고 합니다. 그런데 한 가지 문제가 있습니다. 메인 웹 서버에는 a.epiloum.net 도메인이 연결되어 있는데 반해, 회원명부 DB는 b.epiloum.net 도메인에 연결된 서버에 있다는 것입니다. 동일출처원칙으로 인하여 일반적인 방법으로는 a.epiloum.net의 웹페이지가 b.epiloum.net에 있는 웹페이지를 호출할 수는 없습니다.

(이러한 상황을 타개하는 가장 간편한 방법은, a.epiloum.net에서 b.epiloum.net의 DB를 접근할 수 있도록 접근권한을 조정하는 것입니다. 하지만 웹 서버와 DB 서버가 여러 대로 분산되어 있는 대형 웹 서비스라면, 이것 또한 녹록치 않을 것입니다.)

위와 같은 상황을 JSONP를 통하여 극복해보도록 하겠습니다. 이 예제는 PHP + MySQL 환경을 기준으로 하며, DB 서버로 사용하는 b.epiloum.net에도 웹 서버가 설치되어 있다고 가정할 것입니다.

 

클라이언트측 예제코드 – http://a.epiloum.net/index.html

아래는 JSONP로 데이터를 요청할 검색화면 웹페이지의 예제코드입니다. 이 소스코드 파일은 a.epiloum.net라는 도메인 아래에 위치할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="ko">
<head>
<title>JSONP Sample Code</title>
<meta charset="utf-8">
<script type="text/javascript">
function find()
{
    var j = document.createElement('script');
    var s = encodeURIComponent(document.getElementById('str').value);
 
    j.setAttribute('src', u);
    j.setAttribute('type', 'text/javascript');
    document.getElementsByTagName('body')[0].appendChild(j);
    return false;
}
function loadList(arr)
{
    var o = document.getElementById('list');
    var l;
 
    while(o.childNodes.length)
    {
        o.removeChild(o.lastChild);
    }
 
    for(var i=0; i<arr.length; i++)
    {
        l = document.createElement('li');
        l.appendChild(document.createTextNode(arr[i]));
        o.appendChild(l);
    }
}
</script>
</head>
<body>
<input id="str" type="text" size="50" />
<input id="btn" type="button" value="입력" onclick="return find()" />
<ul id="list"></ul>
</body>
</html>

실제로 코드를 웹 서버에 올려 접속해보시면, 텍스트 입력란과 버튼이 표시된 화면을 볼 수 있습니다. 텍스트 입력란에 적당한 텍스트를 입력하고 버튼을 누르면, find()라는 자바스크립트 함수가 실행될 것입니다. 이 함수는 새로운 <script> 요소를 생성하여 <body> 요소 아래에 추가하는 역할을 합니다.

여기서 주목할 것은 <script>의 src 속성인데요, URL의 도메인이 현재 도메인과는 다른 b.epiloum.net임을 확인할 수 있습니다. GET 파라메터 또한 확인해두셔야 하는데요. callback이라는 파라메터에는 “loadList”라는 문자열이 들어가 있는데, 이것과 같은 이름을 가진 loadList()라는 함수가 있다는 점을 기억하시기 바랍니다. 이어서 s라는 파라메터에는 사용자가 텍스트 입력란에 타이핑한 값이 들어가게 됩니다.

마지막으로 위 예제코드 어디에서도 호출하는 부분이 없는 loadList() 함수가 있습니다. 이 함수는 인자로 받은 배열의 내용을 <ul id=”list”> 요소 아래에 표시하는 역할을 합니다.

 

서버측 예제코드 – http://b.epiloum.net/find.php

이제 앞서의 예제코드에서 <script> 요소를 생성하여 호출하는 find.php의 소스코드를 살펴볼 차례입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?
// Send HTTP Header
header('Content-Type:application/javascript');
// Connect to DB
$dbi = mysql_connect('localhost', '****', '****');
mysql_select_db('****', $dbi);
mysql_query('SET NAMES utf8', $dbi);
// DB Search
$sql = 'SELECT name FROM member WHERE ';
$sql .= 'name LIKE "%' . mysql_real_escape_string($_GET['s']) . '%"';
$res = mysql_query($sql, $dbi);
$dat = array();
while($v = mysql_fetch_assoc($res))
{
    $dat[] = $v['name'];
}
// Output
echo $_GET['callback'] . '(' . json_encode($dat) . ')';
?>

이 소스코드는 회원명부를 담고 있는 `member` 테이블에서, 이름에 해당하는 `name` 필드에 검색어가 포함된 레코드를 LIKE 문을 사용해 검색합니다. 검색어는 GET 파라메터 s를 통해 입력받습니다. 검색한 결과는 PHP 변수인 $dat에 배열로 담아두게 됩니다. 여기까지는 그다지 특이한 것이 없어보입니다.

주목할 곳은 바로 검색결과를 출력하는 부분에 있습니다. 일반적으로 Ajax를 사용할 때는 검색결과를 JSON 형식으로 출력하고 마치는 것이 보통입니다. 그러나 JSONP 방식을 사용한 위 예제코드는 조금 달라보입니다. 바로 GET 파라메터 callback을 통해 전달받은 이름의 함수를 호출하는 자바스크립트 코드를 출력하고 있는 것이죠. 그리고 그 함수의 인자로, 회원명부에서 검색한 결과를 담은 배열이 들어갑니다.

 

예제코드 실행시의 결과

이제 앞서 살펴본 두 소스코드를 연결해서 생각해보면 JSONP의 전말이 드러납니다. 일단 검색화면 웹페이지에서 “john”을 입력하고 버튼을 클릭하면, 아래와 같은 <script> 요소가 생성되었을 것입니다.

<script src=”http://b.epiloum.net/find.php?callback=loadList&s=john”></script>

이렇게 호출된 find.php 파일은 아래와 같이, callback 파라메터로 전달받은 이름인 loadList() 함수를 실행하는 스크립트를 출력합니다. 함수의 인자로 “john”으로 검색한 결과가 배열이 들어오는 것은 물론입니다.

loadList([“John Miller”,”John C. Potter”])

이렇게 검색화면 웹페이지에서 정의한 loadList() 함수가 실행되면, 처음 우리가 살펴보았던대로 <ul id=”list”> 요소 아래에 John이라는 단어로 검색한 결과가 목록으로 보이게 됩니다.

 

JSONP의 기본원리와 한계

위 예제를 토대로 우리는 이제 JSONP가 작동하는 원리를 아래와 같이 정리할 수 있습니다.

  1. 데이터를 요청할 페이지에, 데이터를 받아 처리할 콜백 함수를 먼저 준비해놓습니다. 그 후에 <script> 요소를 생성하여, 데이터 요청을 합니다.
  2. 데이터 요청을 받은 페이지에서는 콜백 함수를 실행하는 스크립트를 출력합니다. 이 때 callback 함수의 인자에는 요청받은 데이터가 들어가게 됩니다.

Ajax와 JSONP 비교 개념도

Ajax와 JSONP 비교 개념도

우리가 잘 알고 있는 Ajax와 비교해보면 흐름 자체가  크게 다르지 않습니다. 단지 콜백함수를 실행하는 타이밍이 달라졌을 뿐입니다. Ajax는 데이터 만을 일단 responseText 속성으로 가져온 후, XMLHttpRequest 객체에서 onreadystatechange 속성에 담긴 콜백함수를 실행합니다. 반면에 JSONP는 서버측에서 이미 콜백함수를 실행하는 코드를 반환하는 점이 다를 뿐입니다. 이처럼 Ajax와 동일한 효과를 가지면서도 동일출처원칙을 회피할 수 있는 것, 이것이 JSONP가 가진 효용성이라고 할 수 있습니다.

한편 Ajax와 비교하여 JSONP가 가지는 한계도 나타나는데, 바로 GET Method만을 사용할 수 있다는 점입니다. 이것은 JSONP가 <script> 요소를 사용하기 때문에 가지는 숙명적인 한계라고 할 수 있습니다.

 

jQuery에서 JSONP 사용하기

금번 포스팅에서는 JSONP의 원리를 설명하기 위하여 Live Javascript로 예제코드를 작성하였지만, 실무에서는 대부분 jQuery를 이용하게 될 것입니다. 위 예제파일의 find() 함수를 jQuery로 변경하면 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function find()
    var s = encodeURIComponent(document.getElementById('str').value);
    
    $.ajax({
        dataType: "jsonp",
        url: "http://b.epiloum.net/find.php",
        type: "GET",
        data: {'s':s},
        success: function(data){
            loadList(data);
        }
    });
 
    return false;
}

jQuery에서는 위처럼 $.ajax() 스펙을 이용하여 평소에 Ajax를 구현하던 것과 동일한 모양으로 JSONP를 구현할 수 있습니다. Ajax와 다른 점은 단지 dataType 값을 “jsonp”로 놓았다는 것 뿐입니다. 위 예제코드를 실행해보면 success 함수가 콜백함수의 역할을 하게 되는 것을 볼 수 있습니다.

다만 이렇게 JSONP를 구현할 경우 3가지 한계가 있습니다. 첫 번째로 type 값은 무조건 GET으로 고정해야 합니다. POST 등으로 설정할 경우, data 값에서 설정한 파라메터들이 실제로 전달되지 않아 정상작동하지 않는 경우가 있습니다.  두 번째로 JSONP를 사용할 경우에는 통신실패시 처리함수인 error 값은 사용할 수 없음을 유념해야 할 것입니다. 마지막으로 url 파라미터로 통신할 파일에서 콜백함수를 받는 GET 파라미터는 반드시 callback으로 고정되어야 합니다.

Cross domain JSON 원리

현재 페이지의 스크립트에서 다른 서버로 Ajax 호출을 시도하는 것은 허용되지 않습니다. 이것을 cross domain JSON을 이용하여 해결할 수 있는데 다음 URL에서 cross domain JSON을 이용한 원격 호출 방법과 간단한 JavaScript 유틸리티를 제시합니다.
그럼 어떻게 JSON을 이용하여 다른 서버의 서비스를 호출할 수 있는지 살펴보겠습니다. 원리를 알면 쉽게 나에게 적합한 유틸리티를 만들수 있을 겁니다.
스크립트 태그
다음은 스크립트 태그입니다. 보통 아래와 같이 사용합니다.
test.js
 
alert(‘OK’);
test.html
<script type=”text/javascript” src=”js/test.js”></script>
그럼 js 디렉토리에 아래와 같은 JavaScript를 생성하는 jsp를 만들고 스크립트 태그에 다음과 같이 수정해봅니다.
test.jsp
 
alert(‘<%=System.getProperty(“os.name”)%>’);
test.html
<script type=”text/javascript” src=”http://userhost:port/js/test.jsp”></script>
System.getProperty(“os.name”)은 Host의 OS 이름을 가져오는 Java 코드이고 서버에서 실행되는 코드입니다.
위와 같이 하면 해당 서버의 OS 이름이 나타날 겁니다.
스크립트 src의 URL에 제약이 없다
위의 test.jsp가 이 서버말고 다른 서버(도메인)에 있더라도 동작합니다.
그럼 test.jsp를 다른 서버에 올려놓고 다음과 같이 스크립트 태그를 수정해 봅니다.
test.html
<script type=”text/javascript” src=”http://remote:port/js/test.jsp”></script>
원격 서버의 OS 이름이 나타날 겁니다.
그럼 test.jsp를 다음과 같은 스크립트를 생성하도록 바꾸고, test.html에는 getHostOs라는 JavaScript 함수를 만들어 놓습니다.
(내서버) test.html
<script type=”text/javascript”>
function getHostOs(response) {
alert(response.osName);
}
</script>
<script type=”text/javascript” src=”http://remote:port/js/test.jsp”></script>
(원격서버) test.jsp
getHostOs({osName:'<%=System.getProperty(“os.name”)%>’});
7번째 줄 javascript 태그에서 원격에서 생성된 스크립트를 실행합니다. 원격 서버에서 생성된 스크립트를 보니 JSON 데이터를 만들고 그걸 파라메터로 getHostOs 라는 함수를 호출하는군요. 이 함수는 이미 페이지에 준비되어 있습니다. (callback 함수라고 합니다.) 즉 아래와 동일한 결과죠.
<script type=”text/javascript”>
function getHostOs(response) {
alert(response.osName);
}
</script>
<script type=”text/javascript”>
getHostOs({osName:’Linux’});
</script>
여기서 관심있게 볼 부분은 8번째 줄 {osName:’Linux’}이 원격에서 생성한 JSON 데이터라는 것입니다.
실전
원격의 JSON 데이터를 가지고 오기 위해서는 <script type… src=”…”></script> 부분이 필요합니다.
원격 서비스 호출을 하는 JavaScript 유틸리티를 만들려면 원격 호출할 때마다 위 방식의 스크립트 태그가 필요하니 동적으로 생성하면 됩니다.
scriptObj = document.createElement(“script”);
scriptObj.setAttribute(“type”, “text/javascript”);
scriptObj.setAttribute(“charset”, “utf-8”);
scriptObj.setAttribute(“src”, remoteUrl);
원격 서비스마다 응답 JSON을 처리하는 방법이 다르므로 callback 함수 이름은 요청마다 다르게 해주어야 하며 원격 서버는 callback 이름을 파라메터로 받아 그 함수이름으로 JSON을 파라메터로 넘기는 스크립트를 생성해주어야 합니다. 즉 callback 함수로 감싸면 됩니다.
http://www.xml.com/pub/a/2005/12/21/json-dynamic-script-tag.html  기사에 있는 예제 http://www.xml.com/2005/12/21/examples/jsr_class.zip 소스를 참고하시면 JSON을 이용한 자신만의 cross domain 호출 유틸리티를 만들 수 있을 겁니다.

 

MSSQL에서 MSSQL Linked Server 설정

*. 연결된 서버 설정 창 “일반” 텝에서 SQL Server Native Client 선택 후 데이터 원본에 IP,PORT 입력

  • 연결된 서버 설정 창 “보안” 텝에서 “다음 보안 컨텍스트를 사용하여 연결” 선택 후 ID/PW 입력

SELECT * FROM OPENQUERY(AKIS_HR, ‘SELECT * FROM [dbo].[Table name]’) WHERE COMPANY_CODE IS NOT NULL;

 

Apache Web Server 2.4 에 HTTPS 적용하기 (CentOS)

설정방법

  1. 의존패키지 설치하기
    sudo yum install openssl-devel
    
  2. Apache 모듈 포함하여 재설치
    CentOS 7 에서 Apache Web Server 2.4 설치하기 참고하여 진행

    sudo ./configure --prefix=[Apache 설치된 디렉토리] --with-apr=[Apache 설치된 디렉토리]/bin/apr-1-config --enable-ssl --with-ssl=/usr/bin/openssl
    sudo make
    sudo make install
    

    prefix를 다른 위치로 지정하여 설치 후 SSL 모듈만 가져와서 사용하는것이 더 안전합니다.

    1. Apache 설정 변경하기
      vi [Apache 설치된 디렉토리]/conf/httpd.conf
      

      httpd.conf 파일에 수정할 내용

      LoadModule socache_shmcb_module modules/mod_socache_shmcb.so //주석 해제
      LoadModule ssl_module modules/mod_ssl.so //추가
      LoadModule rewrite_module modules/mod_rewrite.so //추가 (HTTPS로 리다이렉트시 사용 필요하신 분만 사용하세요.)
      Include conf/extra/httpd-ssl.conf //주석 해제
      
    2. HTTP 접속시 HTTPS로 리다이렉트 하기 위해 httpd.conf파일 마지막에 추가
      <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteCond %{HTTPS} off
      RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [C]
      </IfModule>
      
  3. OpenSSL 인증서 생성하기
    GitLab에 HTTPS 보안 적용을 위한 OpenSSL 설정하기 참고하여 생성
  4. Apache SSL 관련 부분 설정 변경하기
    vi [Apache 설치된 디렉토리]/conf/extra/httpd-ssl.conf
    

    httpd-ssl.conf 파일에 수정할 내용

    JkMountCopy on //<VirtualHost _default_:443>아래에 추가
    DocumentRoot //주석처리
    SSLCertificateFile "[인증서 설치된 디렉토리]/[인증서 이름].crt"
    SSLCertificateKeyFile "[인증서 설치된 디렉토리]/[인증서 이름].key"
    
  5. Tomcat 설정 변경하기
    vi [Tomcat 설치된 디렉토리]/conf/server.xml
    

    server.xml에 변경할 내용

    SSLEngine=off” //기존 "off"를 “on”변경
    
  6. 방화벽 설정하기
    sudo firewall-cmd --add-service=https --permanent
    sudo firewall-cmd --reload
    
  7. Apache, Tomcat 재시작

 Windows 7 Apache와 Tomcat 연동 – 로드 밸런싱

환경 설정

Tomcat 7

Apache 2.4.29

OS windows 7 Win64

 

리케이션을 여러대의 Tomcat으로 구동하는 방식입니다.

Apache 설치

https://www.apachelounge.com/download/

Apache 2.4.29 Win64 용 httpd를 다운받습니다.

이때 mod_jk 파일 mod_jk-1.2.42-win64-VC15.zip도 같이 받으셔야 합니다.

Apache 버전과 Mod_jk 버전이 일치하지 않은경우 

Apache 설치후 구동이 되지 않을수 있습니다.

꼭 호환이 되는 Apache ,Mod_jk 파일을 다운 받으셔야 합니다.

원하는 경로에 Apache 압축을 풀어줍니다.

mod_jk 역시 압축을 풀어줍니다.

mod_jk.so 파일을 

Apache24\moudle 경로에

복사 / 붙여넣기합니다.

시스템 속성 -> 환경변수 설정

Apache 폴더가 존재하는 경로를 입력해줍니다.

C:\Apache24\bin

환경설정 이후

CMD Dos모드로 들어가 

Apache 설치 : httpd -k install

Apache 버전확인 : httpd -v

명령어를 통해 확인합니다.

Apache 설정모드

Apache24\conf 경로 이동후

httpd.conf 파일을 편집합니다.

Editplus 또는 메모장 활용

ServerRoot 수정

Apache 설치경로

ServerName 수정

window -> 서비스 -> Apache24 구동 후 확인

구동이 도지 않으면 Apache24 및 mod_jk를 확인해야 합니다.

Tomcat 로드 밸런싱 설정

저는 Tomcat을 2개 사용할것 입니다.

설치 된 Tomcat 경로로 이동합니다.

Tomcat 설치 경로 이동 후

was1 / was2 라는 빈 폴더를 생성합니다.

Conf / logs / webapps / work 폴더를 복사합니다.

was1 / was2 를 따로 따로 구동하기 위해 필요한 폴더입니다.

기존에 있던 폴더를 복사 하시면 됩니다.

저는 그냥 모든 폴더/파일을 복사 붙여넣기 하였습니다.

물론 Server쪽 환경 설정할때는

Conf / logs / webapps / work 폴더만 붙여넣기 하였습니다.

was1\conf\

Server.xml파일을 편집합니다.

 

<?xml version=’1.0′ encoding=’utf-8′?>

<Server port=”8005″ shutdown=”SHUTDOWN”>

  <Listener className=”org.apache.catalina.startup.VersionLoggerListener” />

  <Listener className=”org.apache.catalina.core.AprLifecycleListener” SSLEngine=”on” />

  <Listener className=”org.apache.catalina.core.JasperListener” />

  <Listener className=”org.apache.catalina.core.JreMemoryLeakPreventionListener” />

  <Listener className=”org.apache.catalina.mbeans.GlobalResourcesLifecycleListener” />

  <Listener className=”org.apache.catalina.core.ThreadLocalLeakPreventionListener” />

  <GlobalNamingResources>

    <Resource name=”UserDatabase” auth=”Container” type=”org.apache.catalina.UserDatabase” description=”User database that can be updated and saved” factory=”org.apache.catalina.users.MemoryUserDatabaseFactory”

              pathname=”conf/tomcat-users.xml” />

  </GlobalNamingResources>

  <Service name=”Catalina”>

<Connector port=”8080″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″/>

<Connector port=”7591″ maxHttpHeaderSize=”8192″ maxKeepAliveRequests=”-1″ maxThreads=”1000″ minSpareThreads=”500″ maxSpareThreads=”500″ enableLoopups=”false” redirectPort=”8443″ acceptCount=”500″

compression=”on” compressionMinSize=”2048″ noCompressionUserAgents=”gozilla.graviata” compressableMimeType=”text/html,text/xml,text/plain,text/javascript,text/css”

connectionTimeout=”600000″ disabledUploadTimeout=”true” Server=”” protocol=”AJP/1.3″  />

<Engine jvmRoute=”tomcat1″ name=”Catalina” defaultHost=”localhost”>

<Realm className=”org.apache.catalina.realm.LockOutRealm”>

<Realm className=”org.apache.catalina.realm.UserDatabaseRealm” resourceName=”UserDatabase”/>

</Realm>

<Host name=”localhost”  appBase=”C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\webapps” unpackWARs=”true” autoDeploy=”true”>

 <Valve className=”org.apache.catalina.valves.AccessLogValve” directory=”logs” prefix=”was1″ suffix=”.txt”  pattern=”%h %l %u %t &quot;%r&quot; %s %b” />

<Context crossContext=”true” path=”” docBase=”/KorailBim” reloadable=”true”></Context>

</Host>

<Cluster className=”org.apache.catalina.ha.tcp.SimpleTcpCluster” channelSendOptions=”8″>

<Manager className=”org.apache.catalina.ha.session.DeltaManager” expireSessionsOnShutdown=”false” notifyListenersOnReplication=”true” />

<Channel className=”org.apache.catalina.tribes.group.GroupChannel”>

<Membership className=”org.apache.catalina.tribes.membership.McastService” address=”228.0.0.4″ port=”45564″ frequency=”500″ dropTime=”3000″ />

<Receiver className=”org.apache.catalina.tribes.transport.nio.NioReceiver” address=”auto” port=”4000″ autoBind=”100″ selectorTimeout=”5000″ maxThreads=”6″ />

<Sender className=”org.apache.catalina.tribes.transport.ReplicationTransmitter”>

<Transport className=”org.apache.catalina.tribes.transport.nio.PooledParallelSender” />

</Sender>

<Interceptor className=”org.apache.catalina.tribes.group.interceptors.TcpFailureDetector” />

<Interceptor className=”org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor” />

</Channel>

<Valve className=”org.apache.catalina.ha.tcp.ReplicationValve” filter=”” />

<Valve className=”org.apache.catalina.ha.session.JvmRouteBinderValve” />

<Deployer className=”org.apache.catalina.ha.deploy.FarmWarDeployer” tempDir=”/tmp/war-temp/” deployDir=”/tmp/war-deploy/” watchDir=”/tmp/war-listen/” watchEnabled=”false” />

<ClusterListener className=”org.apache.catalina.ha.session.ClusterSessionListener” />

</Cluster>

</Engine>

 </Service>

</Server>

—————————————————————————–

웹 어플리케이션이 구동될 Webapps\ROOT으로 이동합니다.

이 경로는 Tomcat -> Server.xml -> appBase 에 설정된 경로의 Root을 사용하면 됩니다.

Web.xml 편집모드로 들어가

<distributable/>

추가해줍니다.

Was2\conf

Server.xml

역시 위와 동일하게 처리해줍니다.

<?xml version=’1.0′ encoding=’utf-8′?>

<Server port=”8006″ shutdown=”SHUTDOWN”>

  <Listener className=”org.apache.catalina.startup.VersionLoggerListener” />

  <Listener className=”org.apache.catalina.core.AprLifecycleListener” SSLEngine=”on” />

  <Listener className=”org.apache.catalina.core.JasperListener” />

  <Listener className=”org.apache.catalina.core.JreMemoryLeakPreventionListener” />

  <Listener className=”org.apache.catalina.mbeans.GlobalResourcesLifecycleListener” />

  <Listener className=”org.apache.catalina.core.ThreadLocalLeakPreventionListener” />

  <GlobalNamingResources>

    <Resource name=”UserDatabase” auth=”Container” type=”org.apache.catalina.UserDatabase”  description=”User database that can be updated and saved” factory=”org.apache.catalina.users.MemoryUserDatabaseFactory”

              pathname=”conf/tomcat-users.xml” />

  </GlobalNamingResources>

  <Service name=”Catalina”>

        <Connector port=”8081″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”8443″/>

<Connector port=”8090″ maxHttpHeaderSize=”8192″ maxKeepAliveRequests=”-1″ maxThreads=”1000″ minSpareThreads=”500″ maxSpareThreads=”500″ enableLoopups=”false” redirectPort=”8443″ acceptCount=”500″

compression=”on” compressionMinSize=”2048″ noCompressionUserAgents=”gozilla.graviata” compressableMimeType=”text/html,text/xml,text/plain,text/javascript,text/css”

connectionTimeout=”600000″ disabledUploadTimeout=”true” Server=”” protocol=”AJP/1.3″  />

<Engine jvmRoute=”tomcat2″ name=”Catalina” defaultHost=”localhost”>

<Realm className=”org.apache.catalina.realm.LockOutRealm”>

<Realm className=”org.apache.catalina.realm.UserDatabaseRealm” resourceName=”UserDatabase”/>

</Realm>

<Host name=”localhost”  appBase=”C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\webapps” unpackWARs=”true” autoDeploy=”true”>

 <Valve className=”org.apache.catalina.valves.AccessLogValve” directory=”logs”

               prefix=”was2″ suffix=”.txt”

               pattern=”%h %l %u %t &quot;%r&quot; %s %b” />

<Context crossContext=”true” path=”” docBase=”/KorailBim” reloadable=”true”></Context>

</Host>

<Cluster className=”org.apache.catalina.ha.tcp.SimpleTcpCluster” channelSendOptions=”8″>

<Manager className=”org.apache.catalina.ha.session.DeltaManager” expireSessionsOnShutdown=”false” notifyListenersOnReplication=”true” />

<Channel className=”org.apache.catalina.tribes.group.GroupChannel”>

<Membership className=”org.apache.catalina.tribes.membership.McastService” address=”228.0.0.4″ port=”45564″ frequency=”500″ dropTime=”3000″ />

<Receiver className=”org.apache.catalina.tribes.transport.nio.NioReceiver” address=”auto” port=”4000″ autoBind=”100″ selectorTimeout=”5000″ maxThreads=”6″ />

<Sender className=”org.apache.catalina.tribes.transport.ReplicationTransmitter”>

<Transport className=”org.apache.catalina.tribes.transport.nio.PooledParallelSender” />

</Sender>

<Interceptor className=”org.apache.catalina.tribes.group.interceptors.TcpFailureDetector” />

<Interceptor className=”org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor” />

</Channel>

<Valve className=”org.apache.catalina.ha.tcp.ReplicationValve” filter=”” />

<Valve className=”org.apache.catalina.ha.session.JvmRouteBinderValve” />

<Deployer className=”org.apache.catalina.ha.deploy.FarmWarDeployer” tempDir=”/tmp/war-temp/” deployDir=”/tmp/war-deploy/” watchDir=”/tmp/war-listen/” watchEnabled=”false” />

<ClusterListener className=”org.apache.catalina.ha.session.ClusterSessionListener” />

</Cluster>

</Engine>

 </Service>

</Server>

———————————————-
Server Port / Connector Port / Cluster Port는
 
was1\Server.xml Port와 중복되지 않게 설정합니다.
 
appBase는 웹 어플리케이션의 위치이기 때문에 
Was1/Was2로 따로 설정해도 무방하지만
배포 처리를 각각해줘야 하기 때문에 
저는 메인 Tomcat 위치로 설정했습니다.
 

다시 Apache24\httpd.conf 파일을 편집하여

Directory경로를 수정해 줍니다.

Apache가 처리할 소스 경로로 Tomcat의 웹 어플리케이션이 존재하는 최상위 폴더를 지정했습니다.

DocumentRoot “C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\webapps”

<Directory “C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\webapps”>

    Require all granted <- 추가

Options Indexes FollowSymLinks

AllowOverride None

Require all granted

</Directory>

Apache24 와 Tomcat의 로드 밸런싱을 위한 설정을 해줘야합니다.

LoadModule jk_module modules/mod_jk.so

JkWorkersFile conf/workers.properties

JkLogFile logs/mod_jk.log

JkLogLevel info

JkLogStampFormat “[%a %b %d %H:%M:%S %Y] ”

JkRequestLogFormat “%w %V %T”

JkMount /  loadbalancer

JkMount /* loadbalancer

AddDefaultCharset UTF-8

conf/workers.properties

파일은 사용자가 직접 빈 파일을 생성해야 합니다.

빈 파일 생성후 편집모드로 작성합니다.

worker.list=loadbalancer, status
worker.tomcat1.port=7591
worker.tomcat1.host=localhost
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor=100
worker.tomcat2.port=8090
worker.tomcat2.host=localhost
worker.tomcat2.type=ajp13
worker.tomcat2.lbfactor=100
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=tomcat1, tomcat2
worker.loadbalancer.method=B
# B(Busyness), R(Requests), T(Traffic)
worker.status.type=status
 
Tomcat\Server.xml에 정의한 
AJP/13  Connector의  jvmRoute의 명칭가 일치시켜 주면 됩니다.
 
was1/Server.xml -> jvmRoute=tomcat1
was1/Server.xml -> port=7591
 
was2/Server.xml -> jvmRoute=tomcat2
was2/Server.xml -> port=8090
이제 마지막으로 was1/was2 실행을 위한 Startup 작업을 해줘야 합니다.

기존에 있던

startup.bat / shutdown.bat

복사 / 붙여넣기를 통해 명칭을 변경합니다.

startup_was1.bat/shutdown_was1.bat

모두편집 모드로 들어가

set “CATALINA_HOMDE=C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81”

set “CATALINA_BASE=C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\was1”

set “SERVER_NAME=was1”

set “JAVA_HOME=C:\Program Files\Java\jdk1.8.0_91”

추가해줍니다.

Apache 설치 및 Was1/was2 구동을 위한 경로

JDK 경로를 설정해줍니다.

————————————-

tartup_was2.bat/shutdown_was2.bat

모두편집 모드로 들어가

set “CATALINA_HOMDE=C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81”

set “CATALINA_BASE=C:\Users\User\Downloads\apache-tomcat-7.0.81-windows-x64\apache-tomcat-7.0.81\was2”

set “SERVER_NAME=was2”

set “JAVA_HOME=C:\Program Files\Java\jdk1.8.0_91”

추가해줍니다.

Apache 설치 및 Was1/was2 구동을 위한 경로

JDK 경로를 설정해줍니다.

Window의 서비스 모드에서

설치된 Apache24를 구동합니다.

마지막으로 Tomcat을 구동하고

localhost:80 포틀 접속하시면 끝이납니다.

한쪽 서버 부하로 인하여 서버가 중단 될경우

다른 한쪽 서버로 웹 어플리케이션이 연동되어

서비스를 운영하는 모드입니다.

테스트 절차는 was1/was2 구동

localhost:80 접속

was로그를 통해 구분 후 접속된 Was 내림

몇초 지난 후

localhost:80 기능 그대로 사용

다른 서버에 붙어서 접속 허용

지금까지

Window Apache Tomcat 로드 밸런싱 연동 과정이었습니다.

출처: https://seungkangmo.tistory.com/173 [Let’s Get It]

Apache24, 톰캣 8.5 연동

2.1 mod_jk 모듈 다운받기

mod_jk는 Apache24에 추가할 수 있는 모듈 중 하나로써,
Apache24에서 다른 웹컨테이너로 처리할 파일을 넘겨줄 수 있게 합니다
https://www.apachelounge.com/download/
링크로 들어가서 스크롤을 살짝 내려보면 Apache 2.4 modules VC14라는 큰 제목이 있고 그 밑에 Apache 2.4에 추가할 수 있는 모듈이 잔뜩 있습니다
그중 mod_jk-1.2.42-win64-VC14.zip
(32bit는 mod_jk-1.2.42-win32-VC14.zip) 를 다운받습니다

압축파일을 열고 mod_jk.so 를
(아파치경로)\modules 폴더 안에 복사합니다
ex) E:\Program Files\Apache24\modules\mod_jk.so

2.2 workers.properties 만들기

(아파치경로)\conf 폴더 안에 텍스트 파일을 만들고 파일 이름과 확장자를 workers.properties로 설정합니다
ex) E:\Program Files\Apache24\conf\workers.properties

workers.properties를 열어서 다음과 같이 작성합니다

worker.list=worker1 

worker.worker1.type=ajp13 

worker.worker1.host=localhost 

worker.worker1.port=8009

2.3 번까지 진행하시면, Apache24는 jsp 파일을 클라이언트로부터 요청받으면 다른 웹컨테이너에게 처리를 떠넘깁니다
이 때 다른 웹컨테이너의 포트번호 등을 작성한 것이 workers.properties 파일입니다
“일을 대신 맡아줄 객체로 worker1를 추가해라. worker1의 type은 ajp13, host는 localhost, 포트번호는 8009이다”

type=ajp13, port=8009를 가진 웹컨테이너는 톰캣이므로 worker1에 작업을 맡기면 톰캣이 그 작업을 처리하게 됩니다

ajp13, port=8009 에 대한 정보는
(톰캣 경로)\conf\server.xml에서 확인할 수 있습니다

2.3 httpd.conf 수정하기

다시
(아파치경로)\conf\httpd.conf 파일을 메모장으로 실행합니다
맨 아래로 스크롤해서 다음을 추가해줍니다

LoadModule jk_module modules/mod_jk.so 
JkWorkersFile conf/workers.properties 
JkLogFile logs/mod_jk.log 
JkLogLevel info 
JkMount /*.jsp worker1

LoadModule로 앞에서 복사해뒀던 mod_jk.so 모듈을 불러옵니다
JkWorkersFile로 작업을 맡길 녀석들의 정보가 담긴 workers.properties 파일을 불러옵니다
JkMount /*.jsp worker1 부분이 젤 중요한데,

/*.jsp: Apache24에서 다른 웹컨테이너에게 맡길 파일 종류를 작성합니다
ex1) JkMount /* worker1
-> worker1으로 지정된 웹컨테이너에게 모든 파일의 처리를 맡깁니다
ex2) JkMount /*.jsp worker1
-> worker1으로 지정된 웹컨테이너에게 jsp 파일의 처리를 맡깁니다

worker1: workers.properties에서 정의한 객체 중 하나로 파일 처리를 맡길 웹컨테이너를 의미합니다
만약 worker1으로 쓰면 worker1에게, worker2로 쓰면 worker2에게 지정한 파일 종류의 처리를 맡깁니다
※ worker1, worker2 등은 작성자가 임의로 지은 변수이름입니다….

3. 잘 된건가??

“제어판-관리도구-서비스”에서 Apache2.4, Apache Tomcat 8.5 서비스를 시작한다
위 첨부파일들을 “1. 경로 설정하기”에서 설정했던 ROOT 경로에 넣고
ex) e:/Server Projects/test.php, e:/Server Projects/test.jsp

localhost/test.php
localhost/test.jsp

를 브라우저 주소창에 쳐본다
숫자 1이 출력되면 성공한 것!
* Apache24 서버가 사용하는 포트를 이용하시면 됩니다
만약 Apache24에 8008 포트를 사용하시면….
localhost:8008/test.php, localhost:8008/test.jsp

4. 잘 안되는 사람들 유형

1. jsp 소스코드가 그대로 출력된다….
연동이 제대로 되지 않은 경우로 Apache24에서 jsp를 톰캣에게 처리를 맡기지 않고 직접 실행한 경우이다
“2.3 httpd.conf 수정”부분을 다시 한번 해본다

2. php 파일실행 시 php 파일이 실행되지 않고 다운로드된다….
Apache24가 php 파일마저 톰캣에게 처리를 맡기는 경우이다
“2.3 httpd.conf수정”에서
JkMount /*.jsp worker1
를 제대로 썼는지 확인해본다

3. 추가로 다음화면은 경로 설정을 잘못해서, 해당 경로에 없는 파일에 접근하려 할 때 나온다

상단 화면이 나타나면 톰캣8.5의 ROOT 경로가 잘못된 경우이고
하단 화면이 나타나면 Apache24의 ROOT 경로가 잘못된 경우이다
“1. 경로 설정하기”에서 경로를 잘 설정했는지 확인해보자