lemon-engine
Version:
Lemon Engine Module to Synchronize Node over DynamoDB + ElastiCache + Elasticsearch by [lemoncloud](https://lemoncloud.io)
218 lines (217 loc) • 11.3 kB
TypeScript
/**
* Lemon Engine Model Of Node (LEMON)
* - Lemon Engine Model Implementation as common core module.
* - 노드의 라이프싸이클을 관리하기 위한 핵심 모델 구현.
*
* ---------------------------
* ## Environment
* - DynamoDB : Main NoSQL Database storage w/ stream.
* - Redis : Internal Quick Cache storage. (Optional)
* - ElasticSearch : Search & Query Service instead of DynamoDB query.
* - SQS (Optional) : Queue task schedule (or buffering).
* - SNS (Optional) : Notification Service.
* - In Memory Cache: Internal Cache Memory for short time cache (@181002)
*
*
* ---------------------------
* ## Node LifeCycle
* - Prepared : 생성된기 전 단계 (ID생성) Next :=> Created
* - Created : 생성되고 활성화 단계. Next :=> Updated, Deleted
* - Updated : 이후 업데이트된 상태. Next :=> Deleted.
* - Deleted : 삭제된 상태. Next :=> Prepared.
*
*
* ---------------------------
* ## Node State (Condition)
* - created_at : millisecond value of UTC+0. (0 means NULL).
* - updated_at : !IMPORTANT! used to track the updated event.
* +-----------------------------------------------------------+
* | State | created_at | updated_at | deleted_at |
* |-----------|---------------|---------------|---------------|
* | Prepared | NULL (or 0) | current | current |
* | Created | current(ms) | current | NULL |
* | Updated | _ | current | _ |
* | Deleted | _ | current | current |
* +-----------------------------------------------------------+
*
*
* ---------------------------
* ## Method 에 대한 정의.
* # 함수의 기본형은 function(id, $node) 이며, 또는 function({id, ...}) 형태도 유효하다
* - prepare() : 데이터 저장을 위한 저장소를 준비. (id가 있을경우 사용하고, 아니면 새로 생성됨) (상태 := Prepared) !중요! DEFAULT 값으로 초기화 됨.
* - create() : 초기 데이터를 생성하는 것으로, 입력 파라미터로 데이터를 저장 시켜 줌. (기본값외의 값이 설정되면, event 로 변경 추적 가능함)
* : 추가 사항! 기존 저장된 노드는 없고, 새로운 ID가 주어진 상태에서 create() 호출하면, 새로운 노드를 생성한다.
* : _force_create = true 일 경우, 상태 무시하고 create로 변경함.
* - clone() : 해당 노드를 클론 함, 입력 파라미터로 클론한 노드의 데이터 초기화 시켜줌 (parent, cloned 를 활용함: see below)
* - update() : 업데이트할 항목만 있음. (때론, 원본과 비교하여 변경된 값을 추적하여 저장할 수 있음)
* - increment() : Atomic Update. {"stock":1,"count":-1} => stock += 1, count -= 1.
* - read() : 현재 노드의 전체 데이터를 읽음. 만일 입력으로 $node 가 있을 경우, Projection 된 부분만 읽어옴 (일부 데이터만). see: auto_populate
* - delete() : 해당 노드를 지운다 (실제로 지우지는 않고 Deleted 상태가 됨).
* - destroy() : 해당 노드를 완전히 지운다 (진짜로 지음).
* - search() : 검색 지원. => ES 에서 지원 함 (Mini Lang). {"name":"<1", "is_soldout":1}
* - initialize() : 주위! Table 생헝하는 등의 자원을 생성한다.
* - terminate() : 주위! 생성된 Table 을 지우는 등, 자원을 제거한다.
* - test_self() : Unit 테스트를 진행한다.
*
* # Dynamo/Kinesis 등 AWS 스트림 전용 처리기.
* - on_records() : Dynamo 에서 Records 정보가 업데이트되어 발생한 이벤트 처리.
*
* # Notify Event 처리 관련.
* - notify() : notify event (async)를 발생시키고, 이벤트 처리기를 호출한다.
* - subscribe() : notify event (async)를 수신할 수 있는 처리기를 등록한다.
*
*
* ---------------------------
* ## Node DataFlow : Save -> Stream -> Sync Cache -> Notify.
* 1. Node 가 업데이트되면, 항상 DynamoDB 에 저장한다.
* - SQS 가 있다는 가정하에, Table 의 업데이트 순서가 보장. (문제점: updated_at의 실제 저장 시점이 시간순으로 안될 수도....)
*
* 2. DynamoDB가 업데이트가 되면, 자동으로 Stream 으로 업데이트 이벤트가 발생함.
* - Local 에서는 Stream 의 Trigger 작동을 수동으로 돌림 (dynamo-stream).
* - AWS 에서는 Stream 을 Lambda 로 연결 (Trim Horizon).
* ex) event.Records.forEach( record => record.dynamodb[''] )
*
* 3. Stream 를 통해서, 업데이트 Node 정보와 캐쉬된 Node 를 비교.
* - Redis.updated_at 과 Stream.updated_at 를 비교
* - Redis 에서 전체 노드를 읽어들이면 느리므로, updated_at만 따로 저장.
*
* 4. 캐쉬된 Node.updated_at <= 업데이트 Node.updated_at 일 경우, 캐쉬를 업데이트 한다.
* - hash(node) != redis.object_hash 이면, 업데이트!
*
* 5. Stream 내용을 이용하여, 변경된 값을 추적하고 -> Notify 할 수 있을 수 있다.
* - Head: state, body: updated set.
*
*
* ---------------------------
* ## In Memory Cache @181002.
* [Problem]
* - Transaction broken as do_read() -> do_update() -> do_read(). due to redis read/write timing.
* [Solution]
* 1. Use internal short memory for node cache with cached_at time (prevent long-term cache, time-out 2 sec)
* 2. Validate primitive types of node-attributes => do update only the updated fields.
*
*
*
* ---------------------------
* ## Event (Records) 처리 방법.
* 1. on_records() 를 통해서, Records[] 정보가 전달됨.. _.each(records, (record) => ...)
* 2. INSERT/REMOVE 일 경우, 그냥 업데이트 실행.
* 3. MODIFY 일 경우:
* - new.updated_at >= redis.updated_at ? 다음 단계 : 무시. (시간 비교)
* - hash(new) != redis.object_hash ? 다음 단계 : 무시. (hash비교하여, redis에서 전체 node를 읽어들이는 오버해드 없앰)
* - save_node (new)
* 4. Notify Event
*
*
* ---------------------------
* ## Clone 처리 방법.
* 1. 해당 Node ID 에서, Clone 을 실행.
* 2. 입력으로 {parent} 정보를 지정할 수 있음. (없을 경우 현재 Node ID 가 parent 됨).
* - parent 는 같은 타입의 valid 한 노드일 경우 가능.
* - Circular 를 방지하기 위해서, parent 는 grand-parent 가 Null 인 경우에 가능.
* - 형제들: 복제된 Node 가 같은 parent 를 공유.
* - 자식들: 복제된 Node 가 해당 Node 를 parent 로 가짐.
* 3. cloned 는 현재 Node ID 로 설정.
* - 복제되면, 추가적으로 that 파라미터에 다음 내용이 추가됨 (if applicable)
* - that.parent := node.parent.
* - that.cloned := node.cloned.
* 4. parent/cloned 는 Immutable 으로, 생성 시점에 지정되며, 이후 변경 불가!!!.
*
*
* ---------------------------
* ## Notify 처리 방법.
* 1. bootup: 내부 핸들러는 부팅때 등록하며, callback 함수로 전달 됨. 형식) subscribe(ID, handler).
* 2. Notify ID 형식은 "서비스 NS+Name" 으로 고유한 값을 가진다. 예) LEM:hello => LEM 모듈에서 hello 라는 것.
* 3. 외부 핸들러는 http URL 전달로 등록 가능. (json 데이터 통신)
* - 그런데, serverless 에서는 내부 저장 유지가 안되므로, 외부 설정이 필요할듯. (TBD)
* 4. Core/Meta 로 분리될 경우, Core 가 업데이트 되었을 때에만 상위로 Notify 를 발생. (ex: ICS -> IIS -> Outter)
*
*
* ---------------------------
* ## 이름규칙 (Naming Rules)
* - item vs item-core : item_id를 id (PK)으로 공유해서 사용한다.
* - item vs deal : id가 서로 다르게 관리됨 (item_id vs deal_id)
* - NoSQL 디비에 저장되는 테이블은 자바 이름 규칙: 예) ItemCoreTable, ItemMetaTable.
* - NoSQL 데이터 변수 이름 규치: 소문자,'_' 구분자. 예) stock, stock_count.
*
*
* ---------------------------
* ## 변수 이름 사용 규칙.
* - $node: NoSQL로 표현되는 데이터의 묶음. (비슷한 의미에서 tuple)
* - xxxx : primitive variable.
* - $xxxx : object variable (internal). ex) prod.$item
* - CAPITAL : object variable (external). ex) prod.ITEM
* - Single CAPITAL char : Reserved variable like version. ex) V: Version, R: Revision.
* - do_xxxx() : public promised function to export.
* - my_xxxx() : private promised function locally.
* - my_chain_xxxx() : internal promised function.
* - on_xxxx() : event consumer.
*
*
* ---------------------------
* ## Node 버전 관리 규칙
* - Version 은 V, Revision 은 R 으로 저장함 (모두 Number 형)
* - Create() 실행시, V := version, R := 0 으로 설정함.
* - Update()/Increment() 실행시 항상 R := R + 1 으로 증분시킴.
* - Version 은 노드 저장 엔진의 버전을 따라감.
*
* ---------------------------
* ## HISTORY
* - 1.0.8 시계열데이터를 ES_TIMESERIES로 지원함 => save() 자동 timestamp 저장하고, DynamoTable 에는 마지막 상태만 저장.
*
* ---------------------------
* ## TODO LIST - LEMON
* - 중요 자원의 History 저장 기능 (Reason CODE, Data) => S3/File/RDB/LogStash.
* - Log with CloudWatch : http://resources.intenseschool.com/amazon-aws-how-to-monitor-log-files-using-cloudwatch-logs/
* - ElasticSearch: ES_FIELDS 항목에서 계산된 결과를 저장 예) deleted := deleted_at > 0
* ! FIELDS 지정할때 데이터 타입(Number, String, Array, Object) 가능하도록.
*
*
* ---------------------------
* ## Example Record.
[ { eventID: '1d444306013fa3c9f49d7acc3b34cdec',
eventName: 'INSERT',
eventVersion: '1.1',
eventSource: 'aws:dynamodb',
awsRegion: 'ap-northeast-2',
dynamodb:
{ ApproximateCreationDateTime: 1502532780,
Keys: [Object],
NewImage: [Object],
SequenceNumber: '300000000000596866043',
SizeBytes: 18,
StreamViewType: 'NEW_AND_OLD_IMAGES' },
eventSourceARN: 'arn:aws:dynamodb:ap-northeast-2:820167020551:table/TestTable/stream/2017-08-12T09:51:57.928' } ]
*
*
*
* @author steve@lemoncloud.io
* @date 2019-05-23
* @copyright (C) lemoncloud.io 2019 - All Rights Reserved.
*/
import { EnginePluggable, EnginePluginBuilder, GeneralFuntion } from '../common/types';
export interface LemonEngineModel extends EnginePluggable {
hello: GeneralFuntion;
do_search: GeneralFuntion;
do_read: GeneralFuntion;
do_readX: GeneralFuntion;
do_read_deep: GeneralFuntion;
do_update: GeneralFuntion;
do_increment: GeneralFuntion;
do_delete: GeneralFuntion;
do_destroy: GeneralFuntion;
do_next_id: GeneralFuntion;
do_prepare: GeneralFuntion;
do_create: GeneralFuntion;
do_clone: GeneralFuntion;
do_initialize: GeneralFuntion;
do_terminate: GeneralFuntion;
on_records: GeneralFuntion;
do_notify: GeneralFuntion;
do_subscribe: GeneralFuntion;
do_test_self: GeneralFuntion;
do_saveES: GeneralFuntion;
do_cleanRedis: GeneralFuntion;
}
declare const buildModel: EnginePluginBuilder<LemonEngineModel>;
export default buildModel;