From 4896615af951bbff940cda7abc116df40ed486e5 Mon Sep 17 00:00:00 2001 From: qile222 Date: Thu, 29 Jun 2023 09:36:40 +0800 Subject: [PATCH] feat: impl transaction decorator (#124) --------- Co-authored-by: ximin.cxm --- core/transaction-decorator/CHANGELOG.md | 4 ++ core/transaction-decorator/README.md | 23 ++++++++ core/transaction-decorator/index.ts | 5 ++ core/transaction-decorator/package.json | 54 +++++++++++++++++++ core/transaction-decorator/src/Common.ts | 11 ++++ .../src/builder/TransactionMetaBuilder.ts | 18 +++++++ .../src/decorator/Transactional.ts | 19 +++++++ .../src/model/TransactionMetadata.ts | 6 +++ .../src/util/TransactionMetadataUtil.ts | 26 +++++++++ .../builder/TransactionMetaBuilder.test.ts | 51 ++++++++++++++++++ .../test/fixtures/transaction.ts | 52 ++++++++++++++++++ core/transaction-decorator/tsconfig.json | 11 ++++ core/transaction-decorator/tsconfig.pub.json | 12 +++++ 13 files changed, 292 insertions(+) create mode 100644 core/transaction-decorator/CHANGELOG.md create mode 100644 core/transaction-decorator/README.md create mode 100644 core/transaction-decorator/index.ts create mode 100644 core/transaction-decorator/package.json create mode 100644 core/transaction-decorator/src/Common.ts create mode 100644 core/transaction-decorator/src/builder/TransactionMetaBuilder.ts create mode 100644 core/transaction-decorator/src/decorator/Transactional.ts create mode 100644 core/transaction-decorator/src/model/TransactionMetadata.ts create mode 100644 core/transaction-decorator/src/util/TransactionMetadataUtil.ts create mode 100644 core/transaction-decorator/test/builder/TransactionMetaBuilder.test.ts create mode 100644 core/transaction-decorator/test/fixtures/transaction.ts create mode 100644 core/transaction-decorator/tsconfig.json create mode 100644 core/transaction-decorator/tsconfig.pub.json diff --git a/core/transaction-decorator/CHANGELOG.md b/core/transaction-decorator/CHANGELOG.md new file mode 100644 index 00000000..e4d87c4d --- /dev/null +++ b/core/transaction-decorator/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/core/transaction-decorator/README.md b/core/transaction-decorator/README.md new file mode 100644 index 00000000..dea662ce --- /dev/null +++ b/core/transaction-decorator/README.md @@ -0,0 +1,23 @@ +# @eggjs/tegg-transaction-decorator + +事务注解 + +## Usage +```ts +export class Foo { + + @Transactional({ propagation: PropagationType.ALWAYS_NEW }) + async bar() { + await this.foo(); + } + + @Transactional({ propagation: PropagationType.REQUIRED }) + async foo(msg) { + console.log('has msg: ', msg); + } + +} + +``` + +Foo.bar 始终会在一个独立的事务中执行,而 Foo.foo 会在 Foo.bar 的事务中执行 diff --git a/core/transaction-decorator/index.ts b/core/transaction-decorator/index.ts new file mode 100644 index 00000000..e9a0e223 --- /dev/null +++ b/core/transaction-decorator/index.ts @@ -0,0 +1,5 @@ +export * from './src/decorator/Transactional'; +export * from './src/Common'; +export * from './src/model/TransactionMetadata'; +export * from './src/builder/TransactionMetaBuilder'; +export * from './src/util/TransactionMetadataUtil'; diff --git a/core/transaction-decorator/package.json b/core/transaction-decorator/package.json new file mode 100644 index 00000000..10972c1c --- /dev/null +++ b/core/transaction-decorator/package.json @@ -0,0 +1,54 @@ +{ + "name": "@eggjs/tegg-transaction-decorator", + "version": "3.9.0", + "description": "tegg transaction decorator", + "keywords": [ + "egg", + "typescript", + "decorator", + "transaction", + "tegg" + ], + "author": "qile222 ", + "homepage": "https://github.com/eggjs/tegg", + "repository": { + "type": "git", + "url": "git@github.com:eggjs/tegg.git", + "directory": "core/transaction-decorator" + }, + "dependencies": { + "@eggjs/core-decorator": "^3.8.0", + "@eggjs/tegg-common-util": "^3.8.0", + "@eggjs/tegg-metadata": "^3.8.0" + }, + "scripts": { + "test": "cross-env NODE_ENV=test NODE_OPTIONS='--no-deprecation' mocha", + "clean": "tsc -b --clean", + "tsc": "npm run clean && tsc -p ./tsconfig.json", + "tsc:pub": "npm run clean && tsc -p ./tsconfig.pub.json", + "prepublishOnly": "npm run tsc:pub" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=14.0.0" + }, + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts" + ], + "bugs": { + "url": "https://github.com/eggjs/tegg/issues" + }, + "devDependencies": { + "@types/mocha": "^10.0.1", + "@types/node": "^20.2.4", + "cross-env": "^7.0.3", + "mocha": "^10.2.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + } +} \ No newline at end of file diff --git a/core/transaction-decorator/src/Common.ts b/core/transaction-decorator/src/Common.ts new file mode 100644 index 00000000..776d705b --- /dev/null +++ b/core/transaction-decorator/src/Common.ts @@ -0,0 +1,11 @@ +export enum PropagationType { + /** 不管是当前调用栈是否存在事务,始终让当前函数在新的事务中执行 */ + ALWAYS_NEW = 'ALWAYS_NEW', + /** 如果当前调用栈存在事务则复用,否则创建一个 */ + REQUIRED = 'REQUIRED', +} + +export interface TransactionalParams { + /** 事务传播方式,默认 REQUIRED */ + propagation?: PropagationType; +} diff --git a/core/transaction-decorator/src/builder/TransactionMetaBuilder.ts b/core/transaction-decorator/src/builder/TransactionMetaBuilder.ts new file mode 100644 index 00000000..5cf7965a --- /dev/null +++ b/core/transaction-decorator/src/builder/TransactionMetaBuilder.ts @@ -0,0 +1,18 @@ +import { EggProtoImplClass } from '@eggjs/tegg'; +import { TransactionMetadata } from '../model/TransactionMetadata'; +import { TransactionMetadataUtil } from '../util/TransactionMetadataUtil'; + +export class TransactionMetaBuilder { + private readonly clazz: EggProtoImplClass; + + constructor(clazz: EggProtoImplClass) { + this.clazz = clazz; + } + + build(): TransactionMetadata[] { + if (!TransactionMetadataUtil.isTransactionClazz(this.clazz)) { + return []; + } + return TransactionMetadataUtil.getTransactionMetadataList(this.clazz); + } +} diff --git a/core/transaction-decorator/src/decorator/Transactional.ts b/core/transaction-decorator/src/decorator/Transactional.ts new file mode 100644 index 00000000..32810454 --- /dev/null +++ b/core/transaction-decorator/src/decorator/Transactional.ts @@ -0,0 +1,19 @@ +import { EggProtoImplClass } from '@eggjs/tegg'; +import { TransactionalParams, PropagationType } from '../Common'; +import { TransactionMetadataUtil } from '../util/TransactionMetadataUtil'; + +export function Transactional(params?: TransactionalParams) { + const propagation = params?.propagation || PropagationType.REQUIRED; + if (!Object.values(PropagationType).includes(propagation)) { + throw new Error(`unknown propagation type ${propagation}`); + } + + return function(target: any, propertyKey: PropertyKey) { + const constructor: EggProtoImplClass = target.constructor; + TransactionMetadataUtil.setIsTransactionClazz(constructor); + TransactionMetadataUtil.addTransactionMetadata(constructor, { + propagation, + method: propertyKey, + }); + }; +} diff --git a/core/transaction-decorator/src/model/TransactionMetadata.ts b/core/transaction-decorator/src/model/TransactionMetadata.ts new file mode 100644 index 00000000..a6514796 --- /dev/null +++ b/core/transaction-decorator/src/model/TransactionMetadata.ts @@ -0,0 +1,6 @@ +import { PropagationType } from '../Common'; + +export interface TransactionMetadata { + propagation: PropagationType; + method: PropertyKey; +} diff --git a/core/transaction-decorator/src/util/TransactionMetadataUtil.ts b/core/transaction-decorator/src/util/TransactionMetadataUtil.ts new file mode 100644 index 00000000..2c91d164 --- /dev/null +++ b/core/transaction-decorator/src/util/TransactionMetadataUtil.ts @@ -0,0 +1,26 @@ +import { EggProtoImplClass, MetadataUtil } from '@eggjs/tegg'; +import { TransactionMetadata } from '../model/TransactionMetadata'; + +export const TRANSACTION_META_DATA = Symbol.for('EggPrototype#transaction#metaData'); +export const IS_TRANSACTION_CLAZZ = Symbol.for('EggPrototype#IS_TRANSACTION_CLAZZ'); + +export class TransactionMetadataUtil { + + static setIsTransactionClazz(clazz: EggProtoImplClass) { + MetadataUtil.defineMetaData(IS_TRANSACTION_CLAZZ, true, clazz); + } + + static isTransactionClazz(clazz: EggProtoImplClass): boolean { + return MetadataUtil.getBooleanMetaData(IS_TRANSACTION_CLAZZ, clazz); + } + + static addTransactionMetadata(clazz: EggProtoImplClass, data: TransactionMetadata) { + const list = MetadataUtil.initOwnArrayMetaData(TRANSACTION_META_DATA, clazz, []); + list.push(data); + } + + static getTransactionMetadataList(clazz: EggProtoImplClass): TransactionMetadata[] { + return MetadataUtil.getArrayMetaData(TRANSACTION_META_DATA, clazz); + } + +} diff --git a/core/transaction-decorator/test/builder/TransactionMetaBuilder.test.ts b/core/transaction-decorator/test/builder/TransactionMetaBuilder.test.ts new file mode 100644 index 00000000..1b5f7466 --- /dev/null +++ b/core/transaction-decorator/test/builder/TransactionMetaBuilder.test.ts @@ -0,0 +1,51 @@ +import assert from 'assert'; +import { TransactionMetadataUtil } from '../../src/util/TransactionMetadataUtil'; +import { TransactionMetaBuilder } from '../../src/builder/TransactionMetaBuilder'; +import { PropagationType } from '../../src/Common'; +import { Foo, Bar, FooBar, BarFoo } from '../fixtures/transaction'; +import { Transactional } from '../../src/decorator/Transactional'; + +describe('test/builder/TransactionMetaBuilder.test.ts', () => { + + it('should build meta data success', () => { + assert.ok(TransactionMetadataUtil.isTransactionClazz(Foo)); + + const fooBuilder = new TransactionMetaBuilder(Foo); + assert.deepStrictEqual(fooBuilder.build(), [{ + propagation: PropagationType.REQUIRED, + method: 'defaultPropagation', + }, { + propagation: PropagationType.REQUIRED, + method: 'requiredPropagation', + }, { + propagation: PropagationType.ALWAYS_NEW, + method: 'alwaysNewPropagation', + }]); + + assert.ok(TransactionMetadataUtil.isTransactionClazz(Bar)); + const barBuilder = new TransactionMetaBuilder(Bar); + assert.deepStrictEqual(barBuilder.build(), [{ + propagation: PropagationType.REQUIRED, + method: 'foo', + }, { + propagation: PropagationType.ALWAYS_NEW, + method: 'bar', + }]); + + assert.ok(TransactionMetadataUtil.isTransactionClazz(FooBar)); + const fooBarBuilder = new TransactionMetaBuilder(FooBar); + assert.deepStrictEqual(fooBarBuilder.build(), [{ + propagation: PropagationType.ALWAYS_NEW, + method: 'foo', + }]); + + const barFooBuilder = new TransactionMetaBuilder(BarFoo); + assert.ok(!TransactionMetadataUtil.isTransactionClazz(BarFoo)); + assert.deepStrictEqual(barFooBuilder.build(), []); + + assert.throws(() => { + Transactional({ propagation: 'xx' as PropagationType }); + }, new Error('unknown propagation type xx')); + }); + +}); diff --git a/core/transaction-decorator/test/fixtures/transaction.ts b/core/transaction-decorator/test/fixtures/transaction.ts new file mode 100644 index 00000000..e8556c69 --- /dev/null +++ b/core/transaction-decorator/test/fixtures/transaction.ts @@ -0,0 +1,52 @@ +import { Transactional } from '../../src/decorator/Transactional'; +import { PropagationType } from '../../src/Common'; + +export class Foo { + + @Transactional() + async defaultPropagation(msg) { + console.log('msg: ', msg); + } + + @Transactional({}) + async requiredPropagation(msg) { + console.log('msg: ', msg); + } + + @Transactional({ propagation: PropagationType.ALWAYS_NEW }) + async alwaysNewPropagation(msg) { + console.log('msg: ', msg); + } + +} + +export class Bar { + + @Transactional() + async foo(msg) { + console.log('msg: ', msg); + } + + @Transactional({ propagation: PropagationType.ALWAYS_NEW }) + async bar(msg) { + console.log('msg: ', msg); + } + +} + +export class FooBar { + + @Transactional({ propagation: PropagationType.ALWAYS_NEW }) + async foo(msg) { + console.log('msg: ', msg); + } + +} + +export class BarFoo { + + async foo(msg) { + console.log('msg: ', msg); + } + +} diff --git a/core/transaction-decorator/tsconfig.json b/core/transaction-decorator/tsconfig.json new file mode 100644 index 00000000..ed206ac6 --- /dev/null +++ b/core/transaction-decorator/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "baseUrl": "./" + }, + "exclude": [ + "dist", + "node_modules" + ] +} diff --git a/core/transaction-decorator/tsconfig.pub.json b/core/transaction-decorator/tsconfig.pub.json new file mode 100644 index 00000000..64b22405 --- /dev/null +++ b/core/transaction-decorator/tsconfig.pub.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "baseUrl": "./" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +}