6. 디자인패턴 1/2
6. 디자인패턴 (1/2) 6 ~ 10
전략(Strategy)
상태(state)
템플릿(Template)
미들웨어(Middleware)
커맨드(Command)
6.6 전략(Strategy)
전체적인 틀 (뼈대)는 유지하고, 알고리즘 변경 및 형태 변경으로 프로세스 처리가 가능한 패턴. 유연성을 확보
한다.
간단 전략패턴 적용 예제
// config.js const fs = require('fs'); const objectPath = require('object-path'); class Config { constructor(strategy) { // 전략 객체 : strategy this.data = {}; this.strategy = strategy; } get(path) { return objectPath.get(this.data, path); } set(path, value) { return objectPath.set(this.data, path, value); } read(file) { console.log(`Deserializing from ${file}`); this.data = this.strategy.deserialize(fs.readFileSync(file, 'utf-8')); } save(file) { console.log(`Serializing to ${file}`); fs.writeFileSync(file, this.strategy.serialize(this.data)); } } module.exports = Config; // strategies.js // json 전략 module.exports.json = { deserialize: data => JSON.parse(data), serialize: data => JSON.stringify(data, null, ' ') }; // ini 전략 (key=value 형식) const ini = require('ini'); module.exports.ini = { deserialize: data => ini.parse(data), serialize: data => ini.stringify(data) }; // configTest const Config = require('./config'); const strategies = require('./strategies'); const jsonConfig = new Config(strategies.json); jsonConfig.read('samples/conf.json'); jsonConfig.set('book.nodejs', 'design patterns'); jsonConfig.save('samples/conf_mod.json'); const iniConfig = new Config(strategies.ini); iniConfig.read('samples/conf.ini'); iniConfig.set('book.nodejs', 'design patterns'); iniConfig.save('samples/conf_mod.ini');
6.7 상태(state)
상태를 하나의 객체화. 동적 변형 및 처리.
호텔예약시스템 -> 3가지의 상태 (confirm-확인, cancel-취소, delete-삭제) 모델링 상태전이, 그다음 상태에 대한 관심사분리
적용 예제
소켓 활성/비활성화를 통한 메시지 큐 처리
// failsafeSocket.js
const OfflineState = require('./offlineState');
const OnlineState = require('./onlineState');
class FailsafeSocket {
constructor(options) {
this.options = options;
this.queue = [];
this.currentState = null;
this.socket = null;
this.states = {
offline: new OfflineState(this),
online: new OnlineState(this)
};
this.changeState('offline');
}
changeState(state) {
console.log('Activating state : ' + state);
this.currentState = this.states[state];
this.currentState.activate();
}
send(data) {
this.currentState.send(data);
}
}
module.exports = options => {
return new FailsafeSocket(options);
}
// online.js
module.exports = class OnlineState {
constructor(failsafeSocket) {
this.failsafeSocket = failsafeSocket;
}
send(data) {
this.failsafeSocket.socket.write(data);
}
activate() {
this.failsafeSocket.queue.forEach(data => {
this.failsafeSocket.socket.write(data);
});
this.failsafeSocket.queue = [];
this.failsafeSocket.socket.once('error', () => {
this.failsafeSocket.changeState('offline')
})
}
};
// offlineState.js
const jot = require('json-over-tcp');
module.exports = class OfflineState {
constructor(failsafeSocket) {
this.failsafeSocket = failsafeSocket;
}
send(data) {
this.failsafeSocket.queue.push(data);
}
activate() {
const retry = () => {
setTimeout(() => this.activate(), 500);
};
this.failsafeSocket.socket = jot.connect(
this.failsafeSocket.options,
() => {
this.failsafeSocket.socket.removeListener('error', retry);
this.failsafeSocket.changeState('online');
}
)
this.failsafeSocket.socket.once('error', retry);
}
};
// server.js
const jot = require('json-over-tcp');
const server = jot.createServer({port:5000});
server.on('connection', socket => {
socket.on('data', data => {
console.log('Client data : ', data)
});
});
server.listen(5000, () => console.log('Stated'));
// client.js
const createFailsafeSocket = require('./failsafeSocket');
const failsafeSocket = createFailsafeSocket({port: 5000});
setInterval(() => {
failsafeSocket.send(process.memoryUsage());
}, 1000);
6.8 템플릿(Template)
템플릿은 상위타입에서 틀이 되는 추상메서드를 제공하고, 하위에서 그 추상메서드를 구현함으로써 동적 실행시 하위 타입에 맞게 실행되게 하는 구조
틀을 제공한다는 관점에서 전략패턴
과 유사하나, 하나의 객체타입이 대체되는 부분과 메서드를 오버라이딩하여 실행하는 관점의 차이가 있다.
그래서 템플릿
은 메서드 내의 알고리즘 순서에 의해 완성되고, 전략
은 객체타입의 변경에 따라 완전 내용이 전반적으로 모든게 바뀌는 느낌
예제
// configTemplate.js
const fs = require('fs');
const objectPath = require('object-path');
class ConfigTemplate {
read(file) {
console.log(`Deserializing from ${file}`);
this.data = this._deserialize(fs.readFileSync(file, 'utf-8'));
}
save(file) {
console.log(`Serializing to ${file}`);
fs.writeFileSync(file, this._serialize(this.data));
}
get(path) {
return objectPath.get(this.data, path);
}
set(path, value) {
return objectPath.set(this.data, path, value);
}
_serialize() {
throw new Error('_serialize() must be implemented');
}
_deserialize() {
throw new Error('_deserialize() must be implemented');
}
}
module.exports = ConfigTemplate;
// jsonConfig.js -> JSON을 처리해주는 하위타입
const util = require('util');
const ConfigTemplate = require('./configTemplate');
class JsonConfig extends ConfigTemplate {
_deserialize(data) {
return JSON.parse(data);
}
_serialize(data) {
return JSON.stringify(data, null, ' ');
}
}
module.exports = JsonConfig;
// iniConfig.js -> ini으로 처리해주는 하위타입
const ConfigTemplate = require('./configTemplate');
const ini = require('ini');
class IniConfig extends ConfigTemplate {
_serialize(data) {
return ini.stringify(data);
}
_deserialize(data) {
return ini.parse(data);
}
}
module.exports = IniConfig;
// test.js
const JsonConfig = require('./jsonConfig');
const jsonConfig = new JsonConfig();
const IniConfig = require('./iniConfig');
const iniConfig = new IniConfig();
jsonConfig.read('samples/conf.json');
jsonConfig.set('nodejs', 'design patterns');
jsonConfig.save('samples/conf_mod.json');
iniConfig.read('samples/conf.ini');
iniConfig.set('nodejs', 'design patterns');
iniConfig.save('samples/conf_mod.ini');
6.9 미들웨어(Middleware)
저수준 추상화 -> OS, API, 네트워크통신, 메모리 관리 등
미들웨어 예 -> CORBA, ENterprise Service Bus, Spring, JBoss, Apache Web Server 등
미들웨어 : 일반적으로 하위서비스
와 어플리케이션
사이의 인터페이스, 연계, 접착제 같은 모든 SW 계층으로 정의 가능
6.9.1 미들웨어로서의 Express
Express에서 미들웨어 -> 파이프라인에서 구성되고 들어오는 HTTP 요청 및 응답의 처리
// req : 요청, res : 응답, next : 미들웨어 (트리거 할때 콜백)
function (req,res,next) {
}
6.9.2 패턴으로서의 미들웨어
Intercepting Filter 패턴과 Chain of Responsibility 패턴의 Node.js화
스트림, 파이프라인 이라고 할 수도 있음
모든 종류의 데이터
에 대한 전처리 및 후처리
를 수행
함수형태로 처리 단위(processing unit), 필터(filter) 및 핸들러(handler) 집합이 비동기 시퀀스의 형태로 연결된 특정 패턴
express -> use함수 : 미들웨어 등록 -> 데이터처리는 비동기 순차실행의 흐름
6.9.3 MQ 용 미들웨어 프레임워크 만들기
메시지큐, 메시징라이브러리를 중심으로 미들웨어 프레임워크를 구축함으로써 패턴을 설명.
분산시스템 데이터 처리에 활용됨
데이터의 전처리 및 후처리 추상화
하는 미들웨어 인프라 구축
// server.js
// const zmq = require('zeromq');
const zmq = require('zmq');
const ZmqMiddlewareManager = require('./zmqMiddlewareManager');
const jsonMiddleware = require('./jsonMiddleware');
const socket = zmq.socket('req');
// const socket = zmq.Push;
socket.bind('tcp://127.0.0.1:5000');
const zmqm = new ZmqMiddlewareManager(socket);
zmqm.use(jsonMiddleware.json());
zmqm.use({
inbound: function (message, next) {
console.log('Received: ', message.data);
if (message.data.action === 'ping') {
this.send({action: 'pong', echo: message.data.echo});
}
next();
}
});
// zmqMiddlewareManager.js
module.exports = class ZmqMiddlewareManager {
constructor(socket) {
this.socket = socket;
this.inboundMiddleware = []; // 들어오는 메시지 처리 (수신메시지)
this.outboundMiddleware = []; // 나가는 메시지 처리 (발신메시지)
socket.on('message', message => {
this.executeMiddleware(this.inboundMiddleware, {
data: message
});
});
// console.log(this.socket.send);;
}
// 새로운 메시지가 소켓을 통해 전송될 때, 미들웨어 역할
send(data) {
const message = {
data
};
// 데이터 보내기, 아웃바운드 처리
this.executeMiddleware(this.outboundMiddleware, message, () => {
this.socket.send(message.data);
});
}
// 미들웨어 형식에 따른 추가
use(middleware) {
if (middleware.inbound) {
this.inboundMiddleware.push(middleware.inbound); // 배열 맨 뒤에 추가
}
if (middleware.outbound) {
this.outboundMiddleware.unshift(middleware.outbound); // 배열 맨 앞에 추가
}
}
// 미들웨어가 처리
executeMiddleware(middleware, arg, finish) {
function iterator(index) {
if (index === middleware.length) {
return finish && finish();
}
middleware[index].call(this, arg, err => {
if (err) {
return console.log('There was an error : ', err.message);
}
iterator.call(this, ++index);
});
}
iterator.call(this, 0);
}
};
// jsonMiddleware.js
module.exports.json = () => {
return {
inbound: function (message, next) {
message.data = JSON.parse(message.data.toString()); // 수신 데이터 Buffer -> JSON
next();
},
outbound: function (message, next) {
message.data = new Buffer(JSON.stringify(message.data)); // 데이터 송신 : JSON -> Buffer
}
}
};
// client.js
const zmq = require('zmq');
const ZmqMiddlewareManager = require('./zmqMiddlewareManager');
const jsonMiddleware = require('./jsonMiddleware');
const request = zmq.socket('req');
request.connect('tcp://127.0.0.1:5000');
const zmqm = new ZmqMiddlewareManager(request);
zmqm.use(jsonMiddleware());
zmqm.use({
inboud: function (message, next) {
console.log('Echoed back : ', message.data);
next();
}
});
setInterval(() => {
zmqm.send({action: 'ping', echo: Date.now()})
});
6.9.4 Koa에서 제너레이터를 사용한 미들웨어
미들웨어 패턴을 많이 사용하는 웹프레임워크 Koa
Koa
: 콜백 사용 대신 제너레이터 함수를 사용하는 미들웨어 패턴을 구현 인바운드(다운스트림), 아웃바운드(업스트림)
예제
// app.js const Koa = require('koa'); const app = new Koa(); app.use(function* () { this.body = {"now": new Date()} // 응답 -> 알아서 JSON 문자열로 변환, 알맞은 content-type 헤더 추가 }); app.use(require('./rateLimit')); app.listen(3000);
6.10 커맨드(Command)
나중에 수행할 동작에 필요한 모든 정보를 캡슐화 하는 객체. 메서드난 함수를 직접 호출하는 대신 위임을 통해 그런 호출을 수행할 목적을 나타내는 객체를 생성.
커맨드(Command) : 메서드 or 함수를 호출하는데
필요한 정보를 캡슐화 하는 객체
클라이언트(Client) :
명령을 생성
하고 그것을호출자에게 제공
호출자(Invoker) : 대상(Subjet)에서
명령을 실행하는 역할
타겟(Target or Receiver) :
호출의 대상
으로 단일 함수 or 한 객체의 메서드
장점
커맨드를
나중에 실행하도록 예약(지연실행)
커맨드는 쉽게 직렬화 되어
네트워크를 통해 전송
,원격 컴터간에 작업을 배포
하고 ,브라우저 -> 서버로 명령을 전송
하고,RPC 시스템을 만드는
등의 작업 수행이 가능커맨드를 사용하면
시스템에서 실행되는 모든 작업의 내역을 쉽게 유지 가능
커맨드는
데이터 동기화 및 충돌 해결
을 위한 일부 알고리즘에서 중요한 부분을 차지실행이 예정된 커맨드가 아직 실행되지 않은 경우 취소 가능
-> 어플리케이션의 상태를 커맨드를 실행하기 전의 상태로 되돌릴 수 있음. (실행쥐소
)몇가지
명령을 그룹화 가능
-> 원자성을 가진트랜잭션
을 만들거나 그룹의 모든작업을 한번에 실행하는 메커니즘 구현
에 사용중복 제거, 결합 및 분할 or 실시간 협업 SW(공동 텍스트 편집 같은) 기반인
운영 변환(Operational Transformation : OT)
같은 복잡한 알고리즘을 적용하는일련의 커맨드들로 다양한 종류의 변환 수행
가능
특히, Node의 네트워킹 및 비동기 실행 필수인 플랫폼에서 중요한 패턴
예제1
// createTast.js function createTask(target, args) { // invoker 역할 return () => { target.apply(null, args); // target 실행 } } module.exports = createTask; // test.js const task = require('./createTask'); // invoker const args = [{say: "hello"}, {say: "world"}]; // 상태정보 let task1 = task((s, s2) => { // callback -> target console.log(s.say + s2.say); }, args); task1();
예제2
// statusUpdate.js -> command
const statusUpdateService = { // target
statusUpdates: {},
// 작업 생성
sendUpdate: function (status) {
console.log('Status sent : ', status);
let id = Math.floor(Math.random() * 1000000);
statusUpdateService.statusUpdates[id] = status;
return id;
},
// 작업 삭제
destroyUpdate: id => {
console.log('Status removed : ', id);
delete statusUpdateService.statusUpdates[id];
}
};
// 커맨드
function createSendStatusCmd(service, status) {
let postId = null;
// 호출될 때 시작하는 함수
const command = () => {
postId = service.sendUpdate(status); // service(target) 상태 업데이트
};
// 동작결과 취소
command.undo = () => {
if (postId) {
service.destroyUpdate(postId); // 취소 (상태정보 없앰)
postId = null;
}
};
// 커맨드 객체 재구성
command.serialize = () => {
return {type: 'status', action: 'post', status}
};
return command;
}
module.exports = {statusUpdateService, createSendStatusCmd};
// invoker.js
class Invoker {
constructor() {
this.history = [];
}
// 실행
run(cmd) {
this.history.push(cmd);
cmd();
console.log('Command executed ', cmd.serialize());
}
// 지연
delay(cmd, delay) {
setTimeout(() => {
this.run(cmd);
}, delay);
}
// 실행 취소
undo() {
const cmd = this.history.pop();
cmd.undo();
console.log('Command undone ', cmd.serialize());
}
// 웹서비스 사용 -> 네트워크 직렬화
runRemotely(cmd) {
request.post('http://localhost:3000/cmd',
{json: cmd.serialize()},
err => {
console.log('Command executed remotely ', cmd.serialize());
}
)
}
}
module.exports = Invoker;
// client.js -> 실행 시작점 (엔트리포인트)
const Invoker = require('./invoker'); // invoker
const {statusUpdateService, createSendStatusCmd} = require('./statusUpdate'); // target, command
const invoker = new Invoker();
const command = createSendStatusCmd(statusUpdateService, "HI~!"); // command
const command2 = createSendStatusCmd(statusUpdateService, "HI2~!"); // command
const command3 = createSendStatusCmd(statusUpdateService, "HI3~!"); // command
invoker.run(command);
invoker.run(command2);
invoker.run(command3);
invoker.undo();
invoker.delay(command, 1000 * 2); // 1시간
invoker.delay(command, 1000 * 2); // 1시간
invoker.delay(command, 1000 * 2); // 1시간
invoker.delay(command, 1000 * 2); // 1시간
// invoker.runRemotely(command);
Last updated
Was this helpful?