UNPKG

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
/** * 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;