Skip to content

Commit 57d9b6f

Browse files
committed
refactor: remove class-based implementation to reduce implementation obfuscation
1 parent 0e74c17 commit 57d9b6f

37 files changed

+353
-276
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<br>
22
<div align="center">
3-
<h1>📦 clean-architecture</h1>
3+
<h1>🏗️ Clean Architecture</h1>
44
<strong>A clean architecture example to implement testable and evolutive systems</strong>
55
</div>
66
<br>

hosts/web/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<br>
22
<div align="center">
3-
<h1>📦 Clean Architecture</h1>
3+
<h1>🏗️ Clean Architecture</h1>
44
<strong>The web host</strong>
55
</div>
66
<br>

modules/catalog/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<br>
22
<div align="center">
3-
<h1>📦 Clean Architecture</h1>
3+
<h1>🏗️ Clean Architecture</h1>
44
<strong>The catalog <a href="https://deviq.com/domain-driven-design/bounded-context">bounded context</a></strong>
55
</div>
66
<br>
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
import { Controller } from "@clean-architecture/shared-kernel";
1+
import type {
2+
Controller,
3+
ControllerFactory,
4+
} from "@clean-architecture/shared-kernel";
25

36
import type { GetQuoteInputData } from "../useCases/GetQuoteUseCase";
47

5-
export class GetQuoteController extends Controller<GetQuoteInputData> {}
8+
export type GetQuoteController = Controller<GetQuoteInputData>;
9+
10+
export const createGetQuoteController: ControllerFactory<
11+
GetQuoteController,
12+
GetQuoteInputData
13+
> = (useCase) => {
14+
return {
15+
async execute(input) {
16+
return useCase.execute(input);
17+
},
18+
};
19+
};
Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
1-
import { Presenter } from "@clean-architecture/shared-kernel";
1+
import type {
2+
Presenter,
3+
PresenterFactory,
4+
} from "@clean-architecture/shared-kernel";
25

36
import type { GetQuoteOutputData } from "../useCases/GetQuoteUseCase";
47
import type { GetQuoteViewModel } from "./GetQuoteViewModel";
58

6-
export class GetQuotePresenter extends Presenter<
9+
export type GetQuotePresenter = Presenter<GetQuoteOutputData>;
10+
11+
export const createGetQuotePresenter: PresenterFactory<
12+
GetQuotePresenter,
713
GetQuoteOutputData,
814
GetQuoteViewModel
9-
> {
10-
public override toViewModel(input: GetQuoteOutputData): GetQuoteViewModel {
11-
if (input.type === "failure") {
12-
return {
15+
> = (onViewModelChange) => {
16+
return {
17+
error(input) {
18+
if (input.type !== "failure") {
19+
throw new RangeError(
20+
"Attempting to convert a success result into an error view model value",
21+
);
22+
}
23+
24+
onViewModelChange({
1325
error: input.payload,
14-
};
15-
}
26+
});
27+
},
28+
ok(input) {
29+
if (input.type !== "success") {
30+
throw new RangeError(
31+
"Attempting to convert a failure result into a success view model value",
32+
);
33+
}
1634

17-
return {
18-
data: input.payload.content,
19-
};
20-
}
21-
}
35+
onViewModelChange({
36+
data: input.payload.content,
37+
});
38+
},
39+
};
40+
};

modules/catalog/src/GetQuote/frameworks/GetQuoteView.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useEffect, useMemo, useState } from "react";
22
import type { Hook } from "@clean-architecture/shared-kernel";
33

4-
import { GetQuoteUseCase } from "../useCases/GetQuoteUseCase";
4+
import { createGetQuoteInteractor } from "../useCases/GetQuoteUseCase";
55
import type { GetQuoteViewModel } from "../adapters/GetQuoteViewModel";
6-
import { GetQuotePresenter } from "../adapters/GetQuotePresenter";
7-
import { GetQuoteController } from "../adapters/GetQuoteController";
6+
import { createGetQuotePresenter } from "../adapters/GetQuotePresenter";
7+
import { createGetQuoteController } from "../adapters/GetQuoteController";
8+
import type { GetQuoteController } from "../adapters/GetQuoteController";
89
import { useDependencyInjection } from "../../shared/frameworks/DependencyInjection";
910

1011
export const GetQuoteView = () => {
@@ -32,16 +33,16 @@ export const GetQuoteView = () => {
3233
const useGetQuote: Hook<GetQuoteController, GetQuoteViewModel> = () => {
3334
const { entityGateway } = useDependencyInjection();
3435
const [viewModel, setViewModel] = useState<GetQuoteViewModel>({});
35-
const presenter = useMemo(() => new GetQuotePresenter(setViewModel), []);
36+
const presenter = useMemo(() => createGetQuotePresenter(setViewModel), []);
3637

37-
const useCase = useMemo(
38-
() => new GetQuoteUseCase(entityGateway, presenter),
38+
const useCaseInteractor = useMemo(
39+
() => createGetQuoteInteractor(entityGateway, presenter),
3940
[entityGateway, presenter],
4041
);
4142

4243
const controller = useMemo(
43-
() => new GetQuoteController(useCase),
44-
[useCase],
44+
() => createGetQuoteController(useCaseInteractor),
45+
[useCaseInteractor],
4546
);
4647

4748
return useMemo(() => {
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
export { GetQuotePresenter } from "./adapters/GetQuotePresenter";
2-
export { GetQuoteController } from "./adapters/GetQuoteController";
3-
export { GetQuoteUseCase } from "./useCases/GetQuoteUseCase";
41
export { GetQuoteView } from "./frameworks/GetQuoteView";
Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { UseCaseInteractor, success } from "@clean-architecture/shared-kernel";
1+
import { success } from "@clean-architecture/shared-kernel";
22
import type {
33
UseCaseInputData,
4+
UseCaseInteractor,
5+
UseCaseInteractorFactory,
46
UseCaseOutputData,
57
} from "@clean-architecture/shared-kernel";
68

@@ -14,22 +16,27 @@ export type GetQuoteOutputData = UseCaseOutputData<{
1416
content: string;
1517
}>;
1618

17-
export class GetQuoteUseCase extends UseCaseInteractor<
19+
export type GetQuoteInteractor = UseCaseInteractor<GetQuoteInputData>;
20+
21+
export const createGetQuoteInteractor: UseCaseInteractorFactory<
22+
GetQuoteInteractor,
1823
GetQuoteInputData,
1924
GetQuoteOutputData,
2025
QuoteEntityGatewayBoundary
21-
> {
22-
public override async execute(input: GetQuoteInputData) {
23-
const entityGatewayResult = await this.entityGateway.getOne(input.id);
26+
> = (entityGateway, presenter) => {
27+
return {
28+
async execute(input) {
29+
const entityGatewayResult = await entityGateway.getOne(input.id);
2430

25-
if (entityGatewayResult.type === "failure") {
26-
this.presenter.error(entityGatewayResult);
27-
} else {
28-
this.presenter.ok(
29-
success({
30-
content: entityGatewayResult.payload.attributes.content,
31-
}),
32-
);
33-
}
34-
}
35-
}
31+
if (entityGatewayResult.type === "failure") {
32+
presenter.error(entityGatewayResult);
33+
} else {
34+
presenter.ok(
35+
success({
36+
content: entityGatewayResult.payload.content,
37+
}),
38+
);
39+
}
40+
},
41+
};
42+
};
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { success } from "@clean-architecture/shared-kernel";
22

33
import type { QuoteEntityGatewayBoundary } from "../entities/QuoteEntityGatewayBoundary";
4-
import { QuoteEntity } from "../entities/QuoteEntity";
5-
import type { QuoteEntityCreateInput } from "../entities/QuoteEntity";
4+
import { createQuoteEntity } from "../entities/QuoteEntity";
65

7-
export class QuoteEntityGateway implements QuoteEntityGatewayBoundary {
8-
public async getMany() {
6+
export const QuoteEntityGateway: QuoteEntityGatewayBoundary = {
7+
async getMany() {
98
await Promise.resolve();
109

1110
return success([]);
12-
}
13-
14-
public async getOne(id: string) {
11+
},
12+
async getOne(id) {
1513
// TODO: use data source interface (that can be implemented by https://dummyjson.com/ or fake concrete implementation) (interface + concrete implementations are not specific to the catalog module, can be implemented in shared kernel?)
1614
const dataSourceOutput = await Promise.resolve({
1715
id,
@@ -20,9 +18,8 @@ export class QuoteEntityGateway implements QuoteEntityGatewayBoundary {
2018
});
2119

2220
return this.toEntity(dataSourceOutput);
23-
}
24-
25-
public toEntity(input: QuoteEntityCreateInput) {
26-
return QuoteEntity.create(input);
27-
}
28-
}
21+
},
22+
toEntity(input) {
23+
return createQuoteEntity(input);
24+
},
25+
};
Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,48 @@
11
import {
2-
Entity,
32
Guard,
4-
IdValueObject,
3+
createEntityFactory,
4+
createIdValueObject,
55
success,
66
} from "@clean-architecture/shared-kernel";
7-
import type { GetValueFromValueObject } from "@clean-architecture/shared-kernel";
7+
import type { Entity } from "@clean-architecture/shared-kernel";
88

9-
import { CreatedAtValueObject } from "../../shared/entities/CreatedAtValueObject";
10-
import { AuthorValueObject } from "../../shared/entities/AuthorValueObject";
9+
import { createCreatedAtValueObject } from "../../shared/entities/CreatedAtValueObject";
10+
import type { CreatedAtValueObject } from "../../shared/entities/CreatedAtValueObject";
11+
import { createAuthorValueObject } from "../../shared/entities/AuthorValueObject";
12+
import type { AuthorValueObject } from "../../shared/entities/AuthorValueObject";
1113

12-
type QuoteEntityAttributes = {
13-
id: IdValueObject;
14+
export type QuoteEntity = Entity<{
1415
author: AuthorValueObject;
1516
content: string;
1617
createdAt: CreatedAtValueObject;
18+
}>;
19+
20+
export type QuoteEntityFactoryInput = Pick<QuoteEntity, "content"> & {
21+
id?: string;
22+
fullName: string;
1723
};
1824

19-
export type QuoteEntityCreateInput =
20-
GetValueFromValueObject<AuthorValueObject> &
21-
Pick<QuoteEntityAttributes, "content"> & {
22-
id?: string;
23-
};
25+
export const createQuoteEntity = createEntityFactory<
26+
QuoteEntity,
27+
QuoteEntityFactoryInput
28+
>((helpers, { id, content, fullName }) => {
29+
const guardContentResult = Guard.mustBeLessThanCharacters(content, 280);
30+
31+
if (guardContentResult.type === "failure") return guardContentResult;
32+
33+
const authorValueObject = createAuthorValueObject({ fullName });
2434

25-
export class QuoteEntity extends Entity<QuoteEntityAttributes> {
26-
private constructor(public override attributes: QuoteEntityAttributes) {
27-
super(attributes);
28-
}
35+
if (authorValueObject.type === "failure") return authorValueObject;
2936

30-
public static override create({
31-
id,
37+
const entity: QuoteEntity = {
38+
id: createIdValueObject(id),
39+
author: authorValueObject.payload,
3240
content,
33-
fullName,
34-
}: QuoteEntityCreateInput) {
35-
const guardContentResult = Guard.mustBeLessThanCharacters(content, 280);
36-
37-
if (guardContentResult.type === "failure") return guardContentResult;
38-
39-
const author = AuthorValueObject.create({ fullName });
40-
41-
if (author.type === "failure") return author;
42-
43-
return success(
44-
new QuoteEntity({
45-
id: IdValueObject.create(id),
46-
author: author.payload,
47-
content,
48-
createdAt: CreatedAtValueObject.create(),
49-
}),
50-
);
51-
}
52-
}
41+
createdAt: createCreatedAtValueObject(undefined),
42+
isEqualTo(input) {
43+
return helpers.isEqualTo(entity, input);
44+
},
45+
};
46+
47+
return success(entity);
48+
});

0 commit comments

Comments
 (0)