Last active
June 15, 2024 12:51
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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