Skip to content

Instantly share code, notes, and snippets.

@hdev14
Last active June 15, 2024 12:51
Show Gist options
  • Save hdev14/433bf5ddcc48eda544822eedd2c4c7ae to your computer and use it in GitHub Desktop.
Save hdev14/433bf5ddcc48eda544822eedd2c4c7ae to your computer and use it in GitHub Desktop.
The real UnitOfWork pattern doesn't use db transaction to commit all changes. This is an idea of how to implement this pattern by adding all changes in memory first and manually commit and rollback the operations.
interface Repository<E extends Entity> {
create(entity: E): Promise<void>;
update(entity: E): Promise<void>;
delete(entity: E): Promise<void>;
}
abstract class Entity {
id: string;
constructor(id: string) {
this.id = id;
}
}
interface UnitOfWork {
register(entity: Entity): Promise<void>;
update(entity: Entity): Promise<void>;
delete(entity: Entity): Promise<void>;
clean(entity: Entity): Promise<void>;
commit(): Promise<void>;
rollback(): Promise<void>;
}
class User extends Entity { }
let count = 0;
class UserRepository implements Repository<User> {
async create(entity: User): Promise<void> {
count++;
console.log('CREATE', entity);
if (count > 1) {
throw new Error(`create error id: ${entity.id}`);
}
}
async update(entity: User): Promise<void> {
console.log('UPDATE', entity);
}
async delete(entity: User): Promise<void> {
console.log('DELETE', entity);
}
}
class DbUnitOfWork implements UnitOfWork {
#inserts: Map<string, Entity> = new Map();
#deletes: Map<string, Entity> = new Map();
#updates: Map<string, Entity> = new Map();
#rollback_inserts: Map<string, Entity> = new Map();
#rollback_deletes: Map<string, Entity> = new Map();
#rollback_updates: Map<string, Entity> = new Map();
#user_repository: Repository<User>;
constructor(user_repository: Repository<User>) {
this.#user_repository = user_repository;
}
async register(entity: Entity): Promise<void> {
if (this.#inserts.has(entity.id)) {
return;
}
if (this.#deletes.has(entity.id)) {
throw new Error('delete conflicts');
}
if (this.#updates.has(entity.id)) {
throw new Error('update conflicts');
}
this.#inserts.set(entity.id, entity);
}
async update(entity: Entity): Promise<void> {
if (this.#deletes.has(entity.id)) {
throw new Error('delete conflicts');
}
this.#updates.set(entity.id, entity);
}
async delete(entity: Entity): Promise<void> {
if (this.#deletes.has(entity.id)) {
return;
}
if (this.#inserts.has(entity.id)) {
throw new Error('inserts conflicts');
}
if (this.#updates.has(entity.id)) {
throw new Error('update conflicts');
}
this.#deletes.set(entity.id, entity);
}
async clean(entity: Entity): Promise<void> {
if (this.#inserts.has(entity.id)) {
this.#inserts.delete(entity.id);
}
if (this.#deletes.has(entity.id)) {
this.#deletes.delete(entity.id);
}
if (this.#updates.has(entity.id)) {
this.#updates.delete(entity.id);
}
}
async commit(): Promise<void> {
for (const entity of this.#inserts.values()) {
if (entity instanceof User) {
await this.#user_repository.create(entity);
}
this.#rollback_inserts.set(entity.id, entity);
}
for (const entity of this.#updates.values()) {
if (entity instanceof User) {
await this.#user_repository.update(entity);
}
this.#rollback_updates.set(entity.id, entity);
}
for (const entity of this.#deletes.values()) {
if (entity instanceof User) {
await this.#user_repository.delete(entity);
}
this.#rollback_deletes.set(entity.id, entity);
}
}
async rollback(): Promise<void> {
for (const entity of this.#rollback_inserts.values()) {
if (entity instanceof User) {
this.#user_repository.delete(entity);
}
}
for (const entity of this.#rollback_updates.values()) {
if (entity instanceof User) {
this.#user_repository.update(entity);
}
}
for (const entity of this.#rollback_deletes.values()) {
if (entity instanceof User) {
this.#user_repository.create(entity);
}
}
}
}
(async () => {
const user1 = new User('1');
const user2 = new User('2');
const user_repository = new UserRepository();
const unitOfWork = new DbUnitOfWork(user_repository);
unitOfWork.register(user1);
unitOfWork.register(user2);
try {
await unitOfWork.commit();
} catch(e: any) {
console.log(e.message);
await unitOfWork.rollback();
}
})();
// https://martinfowler.com/eaaCatalog/unitOfWork.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment