diff --git a/hosts/web/index.html b/hosts/web/index.html index 348d159..7d055c0 100644 --- a/hosts/web/index.html +++ b/hosts/web/index.html @@ -11,7 +11,7 @@ name="viewport" content="width=device-width, initial-scale=1.0" /> - Vite + React + TS + Web
diff --git a/modules/catalog/src/adapters/QuoteEntityGateway.ts b/modules/catalog/src/adapters/QuoteEntityGateway.ts index e8c3d5f..44f8e45 100644 --- a/modules/catalog/src/adapters/QuoteEntityGateway.ts +++ b/modules/catalog/src/adapters/QuoteEntityGateway.ts @@ -1,9 +1,7 @@ import { success } from "@clean-architecture/shared-kernel"; -import type { IdValueObject } from "@clean-architecture/shared-kernel"; import type { QuoteEntityGatewayPort } from "../entities/QuoteEntityGatewayPort"; import { QuoteEntity } from "../entities/QuoteEntity"; -import { AuthorValueObject } from "../entities/AuthorValueObject"; export class QuoteEntityGateway implements QuoteEntityGatewayPort { public async getMany() { @@ -12,17 +10,17 @@ export class QuoteEntityGateway implements QuoteEntityGatewayPort { return success([]); } - public async getOne(id: IdValueObject) { + public async getOne(id: string) { await Promise.resolve(); - // TODO: refacto to be internalized inside the QuoteEntity (to prevent anemic model) - const author = AuthorValueObject.create({ - firstName: "test", - lastName: "test", - }); - - if (author.type === "failure") return author; + const fullName = "Test Test"; + const [firstName, lastName] = fullName.split(" ") as [string, string]; - return QuoteEntity.create(id, author.payload, "Fake content"); + return QuoteEntity.create({ + id, + content: "Fake content", + firstName, + lastName, + }); } } diff --git a/modules/catalog/src/entities/QuoteEntity.ts b/modules/catalog/src/entities/QuoteEntity.ts index 360442d..e1912c6 100644 --- a/modules/catalog/src/entities/QuoteEntity.ts +++ b/modules/catalog/src/entities/QuoteEntity.ts @@ -1,30 +1,52 @@ -import { Entity, Guard, success } from "@clean-architecture/shared-kernel"; -import type { IdValueObject } from "@clean-architecture/shared-kernel"; +import { + Entity, + Guard, + IdValueObject, + success, +} from "@clean-architecture/shared-kernel"; +import type { GetValueFromValueObject } from "@clean-architecture/shared-kernel"; import { CreatedAtValueObject } from "./CreatedAtValueObject"; -import type { AuthorValueObject } from "./AuthorValueObject"; - -export class QuoteEntity extends Entity { - public createdAt: CreatedAtValueObject; - - private constructor( - public override id: IdValueObject, - public author: AuthorValueObject, - public content: string, - ) { - super(id); - this.createdAt = CreatedAtValueObject.create(); +import { AuthorValueObject } from "./AuthorValueObject"; + +type QuoteEntityAttributes = { + id: IdValueObject; + author: AuthorValueObject; + content: string; + createdAt: CreatedAtValueObject; +}; + +type QuoteEntityCreateInput = GetValueFromValueObject & + Pick & { + id: string; + }; + +export class QuoteEntity extends Entity { + private constructor(public override attributes: QuoteEntityAttributes) { + super(attributes); } - public static override create( - id: IdValueObject, - author: AuthorValueObject, - content: string, - ) { - const guardResult = Guard.mustBeLessThanCharacters(content, 280); + public static override create({ + id, + content, + firstName, + lastName, + }: QuoteEntityCreateInput) { + const guardContentResult = Guard.mustBeLessThanCharacters(content, 280); + + if (guardContentResult.type === "failure") return guardContentResult; + + const author = AuthorValueObject.create({ firstName, lastName }); - if (guardResult.type === "failure") return guardResult; + if (author.type === "failure") return author; - return success(new QuoteEntity(id, author, content)); + return success( + new QuoteEntity({ + id: IdValueObject.create(id), + author: author.payload, + content, + createdAt: CreatedAtValueObject.create(), + }), + ); } } diff --git a/modules/catalog/src/useCases/GetQuoteUseCase.ts b/modules/catalog/src/useCases/GetQuoteUseCase.ts index 8150958..52908a4 100644 --- a/modules/catalog/src/useCases/GetQuoteUseCase.ts +++ b/modules/catalog/src/useCases/GetQuoteUseCase.ts @@ -1,8 +1,4 @@ -import { - IdValueObject, - UseCaseInteractor, - success, -} from "@clean-architecture/shared-kernel"; +import { UseCaseInteractor, success } from "@clean-architecture/shared-kernel"; import type { RequestModel, ResponseModel, @@ -24,14 +20,17 @@ export class GetQuoteUseCase extends UseCaseInteractor< QuoteEntityGatewayPort > { public override async execute(requestModel: GetQuoteRequestModel) { - const id = IdValueObject.create(requestModel.id); - const entityGatewayResult = await this.entityGateway.getOne(id); + const entityGatewayResult = await this.entityGateway.getOne( + requestModel.id, + ); if (entityGatewayResult.type === "failure") { this.presenter.error(entityGatewayResult); } else { this.presenter.ok( - success({ content: entityGatewayResult.payload.content }), + success({ + content: entityGatewayResult.payload.attributes.content, + }), ); } } diff --git a/modules/shared-kernel/src/Entity.ts b/modules/shared-kernel/src/Entity.ts index 384d426..5aa44ae 100644 --- a/modules/shared-kernel/src/Entity.ts +++ b/modules/shared-kernel/src/Entity.ts @@ -1,27 +1,37 @@ import type { Result } from "@open-vanilla/result"; -import { IdValueObject } from "./IdValueObject"; +import type { GetValueFromValueObject } from "./ValueObject"; +import type { IdValueObject } from "./IdValueObject"; import { Guard } from "./Guard"; import type { DomainObject } from "./DomainObject"; -export abstract class Entity implements DomainObject { - protected constructor( - public id: IdValueObject = IdValueObject.create(crypto.randomUUID()), - ) {} +type EntityAttributes = { id: IdValueObject }; - public static create(..._: unknown[]): Entity | Result { +export abstract class Entity< + Attributes extends EntityAttributes = EntityAttributes, +> implements DomainObject +{ + protected constructor(public attributes: Attributes) {} + + public static create(_input: { + id: GetValueFromValueObject; + }): Entity | Result { throw new Error("NotImplementedException"); } + public static isInstanceOf(input: unknown): input is Entity { + return input instanceof Entity; + } + public equals(input: unknown) { if (this === input) return true; if ( - !(input instanceof Entity) || + !Entity.isInstanceOf(input) || Guard.mustBeDefinedAndNonNull(input).type === "failure" ) return false; - return this.id.equals(input.id); + return this.attributes.id.equals(input.attributes.id); } } diff --git a/modules/shared-kernel/src/EntityGateway.ts b/modules/shared-kernel/src/EntityGateway.ts index 88d0c63..882e565 100644 --- a/modules/shared-kernel/src/EntityGateway.ts +++ b/modules/shared-kernel/src/EntityGateway.ts @@ -1,6 +1,7 @@ import type { Result } from "@open-vanilla/result"; import type { AnyRecord } from "./types"; +import type { GetValueFromValueObject } from "./ValueObject"; import type { Entity } from "./Entity"; export type EntityGateway< @@ -8,5 +9,7 @@ export type EntityGateway< Methods = AnyRecord, > = Methods & { getMany: () => Promise>; - getOne: (id: E["id"]) => Promise>; + getOne: ( + id: GetValueFromValueObject, + ) => Promise>; }; diff --git a/modules/shared-kernel/src/IdValueObject.ts b/modules/shared-kernel/src/IdValueObject.ts index 8c63014..1ccde2c 100644 --- a/modules/shared-kernel/src/IdValueObject.ts +++ b/modules/shared-kernel/src/IdValueObject.ts @@ -10,7 +10,7 @@ type Value = string; * if they are both strings but impossible if they are represented through a dedicated type)). */ export class IdValueObject extends ValueObject { - public static override create(input: Value) { + public static override create(input: Value = crypto.randomUUID()) { return new IdValueObject(input); } } diff --git a/modules/shared-kernel/src/ValueObject.ts b/modules/shared-kernel/src/ValueObject.ts index d7c3982..c9330c5 100644 --- a/modules/shared-kernel/src/ValueObject.ts +++ b/modules/shared-kernel/src/ValueObject.ts @@ -24,7 +24,7 @@ export abstract class ValueObject implements DomainObject { public readonly value: Value; public static create( - ..._: unknown[] + _input: GetValueFromValueObject>, ): Result> | ValueObject { throw new Error("NotImplementedException"); } @@ -42,3 +42,6 @@ export abstract class ValueObject implements DomainObject { return JSON.stringify(this) === JSON.stringify(input); } } + +export type GetValueFromValueObject> = + Input["value"]; diff --git a/modules/shared-kernel/src/index.ts b/modules/shared-kernel/src/index.ts index 0f143a7..3e4143b 100644 --- a/modules/shared-kernel/src/index.ts +++ b/modules/shared-kernel/src/index.ts @@ -5,6 +5,7 @@ export { Guard } from "./Guard"; export { IdValueObject } from "./IdValueObject"; export { Presenter } from "./Presenter"; export { UseCaseInteractor } from "./UseCase"; +export type { GetValueFromValueObject } from "./ValueObject"; export { ValueObject } from "./ValueObject"; export type { RequestModel } from "./RequestModel"; export type { ResponseModel } from "./ResponseModel";