Skip to content

Commit

Permalink
[db] Allow to share transaction across DB impls
Browse files Browse the repository at this point in the history
  • Loading branch information
geropl authored and roboquat committed Nov 26, 2021
1 parent cad1b9e commit 9e41eb0
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 12 deletions.
2 changes: 1 addition & 1 deletion components/ee/payment-endpoint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"build:clean": "yarn clean && yarn build",
"rebuild": "yarn build:clean",
"build:watch": "watch 'yarn build' .",
"watch": "yarn tsc -w",
"watch": "leeway exec --package .:app --transitive-dependencies --filter-type yarn --components --parallel -- tsc -w --preserveWatchOutput",
"clean": "yarn run rimraf lib",
"clean:node": "yarn run rimraf node_modules",
"purge": "yarn clean && yarn clean:node && yarn run rimraf yarn.lock",
Expand Down
9 changes: 7 additions & 2 deletions components/gitpod-db/src/accounting-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

import { AccountEntry, Subscription, SubscriptionAndUser, Credit } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
import { DBSubscriptionAdditionalData, DBPaymentSourceInfo } from "./typeorm/entity/db-subscription";
import { DeepPartial } from "typeorm";
import { DeepPartial, EntityManager } from "typeorm";

export const TransactionalAccountingDBFactory = Symbol('TransactionalAccountingDBFactory');
export interface TransactionalAccountingDBFactory {
(manager: EntityManager): AccountingDB;
}

export const AccountingDB = Symbol('AccountingDB');

Expand All @@ -33,7 +38,7 @@ export interface AccountingDB {
hadSubscriptionCreatedWithCoupon(userId: string, coupon: string): Promise<boolean>;
findSubscriptionAdditionalData(paymentReference: string): Promise<DBSubscriptionAdditionalData | undefined>;

transaction<T>(code: (db: AccountingDB)=>Promise<T>): Promise<T>;
transaction<T>(closure: (db: AccountingDB)=>Promise<T>, closures?: ((manager: EntityManager) => Promise<any>)[]): Promise<T>;

storeSubscriptionAdditionalData(subscriptionData: DBSubscriptionAdditionalData): Promise<DBSubscriptionAdditionalData>;
storePaymentSourceInfo(cardInfo: DBPaymentSourceInfo): Promise<DBPaymentSourceInfo>;
Expand Down
19 changes: 15 additions & 4 deletions components/gitpod-db/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { AppInstallationDB } from './app-installation-db';
import { TheiaPluginDBImpl } from './typeorm/theia-plugin-db-impl';
import { TheiaPluginDB } from './theia-plugin-db';
import { TypeORMOneTimeSecretDBImpl } from './typeorm/one-time-secret-db-impl';
import { PendingGithubEventDB } from './pending-github-event-db';
import { TypeORMPendingGithubEventDBImpl } from './typeorm/pending-github-event-db-impl';
import { PendingGithubEventDB, TransactionalPendingGithubEventDBFactory } from './pending-github-event-db';
import { TransactionalPendingGithubEventDBImpl, TypeORMPendingGithubEventDBImpl } from './typeorm/pending-github-event-db-impl';
import { GitpodTableDescriptionProvider, TableDescriptionProvider } from './tables';
import { PeriodicDbDeleter } from './periodic-deleter';
import { TermsAcceptanceDB } from './terms-acceptance-db';
Expand All @@ -39,7 +39,7 @@ import { AuthCodeRepositoryDB } from './typeorm/auth-code-repository-db';
import { AuthProviderEntryDB } from './auth-provider-entry-db';
import { AuthProviderEntryDBImpl } from './typeorm/auth-provider-entry-db-impl';
import { TeamSubscriptionDB } from './team-subscription-db';
import { AccountingDB } from './accounting-db';
import { AccountingDB, TransactionalAccountingDBFactory } from './accounting-db';
import { EmailDomainFilterDB } from './email-domain-filter-db';
import { EduEmailDomainDB } from './edu-email-domain-db';
import { EMailDB } from './email-db';
Expand All @@ -49,11 +49,12 @@ import { TypeORMEMailDBImpl } from './typeorm/email-db-impl';
import { EduEmailDomainDBImpl } from './typeorm/edu-email-domain-db-impl';
import { EmailDomainFilterDBImpl } from './typeorm/email-domain-filter-db-impl';
import { TeamSubscriptionDBImpl } from './typeorm/team-subscription-db-impl';
import { TypeORMAccountingDBImpl } from './typeorm/accounting-db-impl';
import { TransactionalAccountingDBImpl, TypeORMAccountingDBImpl } from './typeorm/accounting-db-impl';
import { TeamDB } from './team-db';
import { TeamDBImpl } from './typeorm/team-db-impl';
import { ProjectDB } from './project-db';
import { ProjectDBImpl } from './typeorm/project-db-impl';
import { EntityManager } from 'typeorm';

// THE DB container module that contains all DB implementations
export const dbContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
Expand Down Expand Up @@ -93,6 +94,11 @@ export const dbContainerModule = new ContainerModule((bind, unbind, isBound, reb

bind(TypeORMPendingGithubEventDBImpl).toSelf().inSingletonScope();
bind(PendingGithubEventDB).toService(TypeORMPendingGithubEventDBImpl);
bind(TransactionalPendingGithubEventDBFactory).toFactory(ctx => {
return (manager: EntityManager) => {
return new TransactionalPendingGithubEventDBImpl(manager);
}
});

encryptionModule(bind, unbind, isBound, rebind);
bind(KeyProviderConfig).toDynamicValue(ctx => {
Expand All @@ -119,6 +125,11 @@ export const dbContainerModule = new ContainerModule((bind, unbind, isBound, reb

// com concerns
bind(AccountingDB).to(TypeORMAccountingDBImpl).inSingletonScope();
bind(TransactionalAccountingDBFactory).toFactory(ctx => {
return (manager: EntityManager) => {
return new TransactionalAccountingDBImpl(manager);
}
});
bind(TeamSubscriptionDB).to(TeamSubscriptionDBImpl).inSingletonScope();
bind(EmailDomainFilterDB).to(EmailDomainFilterDBImpl).inSingletonScope();
bind(EduEmailDomainDB).to(EduEmailDomainDBImpl).inSingletonScope();
Expand Down
9 changes: 9 additions & 0 deletions components/gitpod-db/src/pending-github-event-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
*/

import { PendingGithubEvent, User, Identity } from "@gitpod/gitpod-protocol";
import { EntityManager } from "typeorm";

export type PendingGithubEventWithUser = PendingGithubEvent & { identity: Identity & { user: User } };

export const TransactionalPendingGithubEventDBFactory = Symbol('TransactionalPendingGithubEventDBFactory');
export interface TransactionalPendingGithubEventDBFactory {
(manager: EntityManager): PendingGithubEventDB;
}

export const PendingGithubEventDB = Symbol('PendingGithubEventDB');
export interface PendingGithubEventDB {
store(evt: PendingGithubEvent): Promise<void>;
Expand All @@ -19,4 +25,7 @@ export interface PendingGithubEventDB {
* when the event arrived. This function finds all pending events for which a user exists now.
*/
findWithUser(type: string): Promise<PendingGithubEventWithUser[]>;


transaction<T>(code: (db: PendingGithubEventDB) => Promise<T>): Promise<T>;
}
15 changes: 11 additions & 4 deletions components/gitpod-db/src/typeorm/accounting-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License.enterprise.txt in the project root folder.
*/

import { AccountingDB } from "../accounting-db";
import { AccountingDB, TransactionalAccountingDBFactory } from "../accounting-db";
import { DBAccountEntry } from "./entity/db-account-entry";
import { User } from "@gitpod/gitpod-protocol";
import { AccountEntry, Subscription, Credit, SubscriptionAndUser } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
Expand All @@ -19,12 +19,19 @@ import { TypeORM } from "./typeorm";
export class TypeORMAccountingDBImpl implements AccountingDB {

@inject(TypeORM) typeORM: TypeORM;
@inject(TransactionalAccountingDBFactory) protected readonly transactionalFactory: TransactionalAccountingDBFactory;

async transaction<T>(code: (db: AccountingDB) => Promise<T>): Promise<T> {
async transaction<T>(closure: (db: AccountingDB) => Promise<T>, closures?: ((manager: EntityManager) => Promise<any>)[]): Promise<T> {
const manager = await this.getEntityManager();
return await manager.transaction(async manager => {
return await code(new TransactionalAccountingDBImpl(manager));
})
const transactionDB = this.transactionalFactory(manager);
const result = await closure(transactionDB);

for (const c of (closures || [])) {
await c(manager);
}
return result;
});
}

protected async getEntityManager() {
Expand Down
26 changes: 25 additions & 1 deletion components/gitpod-db/src/typeorm/pending-github-event-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { inject, injectable } from "inversify";
import { PendingGithubEvent } from "@gitpod/gitpod-protocol";
import { EntityManager, Repository } from "typeorm";
import { TypeORM } from './typeorm';
import { PendingGithubEventDB, PendingGithubEventWithUser } from "../pending-github-event-db";
import { PendingGithubEventDB, PendingGithubEventWithUser, TransactionalPendingGithubEventDBFactory } from "../pending-github-event-db";
import { DBPendingGithubEvent } from "./entity/db-pending-github-event";
import { DBIdentity } from "./entity/db-identity";

@injectable()
export class TypeORMPendingGithubEventDBImpl implements PendingGithubEventDB {
@inject(TypeORM) protected readonly typeorm: TypeORM;
@inject(TransactionalPendingGithubEventDBFactory) protected readonly transactionalFactory: TransactionalPendingGithubEventDBFactory;

protected async getManager(): Promise<EntityManager> {
return (await this.typeorm.getConnection()).manager;
Expand Down Expand Up @@ -55,4 +56,27 @@ export class TypeORMPendingGithubEventDBImpl implements PendingGithubEventDB {
return res as PendingGithubEventWithUser[];
}

async transaction<T>(code: (db: PendingGithubEventDB) => Promise<T>): Promise<T> {
const manager = await this.getManager();
return await manager.transaction(async manager => {
const transactionalDB = this.transactionalFactory(manager);
return await code(transactionalDB);
});
}
}

export class TransactionalPendingGithubEventDBImpl extends TypeORMPendingGithubEventDBImpl {

constructor(
protected readonly manager: EntityManager) {
super();
}

protected async getManager() {
return this.manager;
}

public async transaction<T>(code: (sb: PendingGithubEventDB) => Promise<T>): Promise<T> {
return await code(this);
}
}

0 comments on commit 9e41eb0

Please sign in to comment.