-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor/hexagonal pattern poc v2 #235
Changes from 24 commits
c165af7
4a261bb
480996a
ee52014
c1e39ff
673017d
b882198
fe1455f
e148ba1
775d568
77393e3
e8800cf
75aab33
de50783
7065ad1
16865ee
83dc75e
b5dcce9
effa6d9
2a72351
6b789c3
98f341b
5972f54
99ae94b
1c6d399
5109f3d
f9a2a4c
a857f86
5641cfb
2ab7364
8aa077f
23f220a
eaa93a9
2f622a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,12 @@ import { getAccessToken } from "@/utils/getCookie"; | |
import { DELETE, PATCH, POST } from "@/utils/requests"; | ||
import { type AsyncActionResponse, handleAsync } from "@/utils/handleAsync"; | ||
import { CacheTag } from "@/utils/cacheTag"; | ||
import { type AddIdeationResponseDto } from "@/modules/ideation/application/dtos/response.dto"; | ||
import { NextJsRestApiAdapter } from "@/modules/restApi/adapters/secondary/nextJsRestApiAdapter"; | ||
import { IdeationApiAdapter } from "@/modules/ideation/adapters/secondary/ideationApiAdapter"; | ||
import { type AddIdeationUsecaseDto } from "@/modules/ideation/application/dtos/addIdeationUsecaseDto"; | ||
import { AddIdeationUseCase } from "@/modules/ideation/application/usecases/addIdeationUseCase"; | ||
import { type AddIdeationRequestDto } from "@/modules/ideation/application/dtos/request.dto"; | ||
|
||
interface IdeationProps { | ||
teamId: number; | ||
|
@@ -53,23 +59,43 @@ export interface IdeationVoteResponse extends IdeationResponse { | |
projectIdeaId: number; | ||
} | ||
|
||
const nextJsRestApiAdapter = new NextJsRestApiAdapter( | ||
process.env.NEXT_PUBLIC_API_URL!, | ||
); | ||
const ideationApiPort = new IdeationApiAdapter(nextJsRestApiAdapter); | ||
|
||
interface NextJsAddIdeationAdapter extends AddIdeationUseCase { | ||
execute( | ||
props: Required<AddIdeationRequestDto>, | ||
): Promise<AddIdeationResponseDto>; | ||
} | ||
|
||
export async function addIdeation({ | ||
teamId, | ||
title, | ||
description, | ||
vision, | ||
}: AddIdeationProps): Promise<AsyncActionResponse<AddIdeationResponse>> { | ||
}: AddIdeationUsecaseDto): Promise< | ||
AsyncActionResponse<AddIdeationResponseDto> | ||
> { | ||
const token = getAccessToken(); | ||
|
||
const addIdeationAsync = () => | ||
POST<AddIdeationBody, AddIdeationResponse>( | ||
`api/v1/voyages/teams/${teamId}/ideations`, | ||
const addIdeationUseCase: NextJsAddIdeationAdapter = new AddIdeationUseCase( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use the "const { Class } = resolveInjection()" (server side) method to import the class from the tsyringe container and use it here or in a component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a stupid example from another project: // CLIENT SIDE
// SERVER SIDE const { classUseCase } = resolveInjection(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do we resolve the dependency of that usecase? For example in the add ideation usecase, it needs to inject the ideationApi (type ideationApiPort) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as you can see I instantiate it like const addIdeationUseCase = new AddIdeationUseCase(ideationApiPort); |
||
ideationApiPort, | ||
); | ||
|
||
// refactor this later to not expect a function | ||
const addIdeationAsync = async () => | ||
await addIdeationUseCase.execute({ | ||
teamId, | ||
title, | ||
description, | ||
vision, | ||
cache: "default", | ||
token, | ||
"default", | ||
{ title, description, vision }, | ||
); | ||
}); | ||
|
||
const [res, error] = await handleAsync(addIdeationAsync); | ||
const [res, error] = | ||
await handleAsync<AddIdeationResponseDto>(addIdeationAsync); | ||
|
||
if (res) { | ||
revalidateTag(CacheTag.ideation); | ||
|
@@ -78,6 +104,32 @@ export async function addIdeation({ | |
return [res, error]; | ||
} | ||
|
||
// test | ||
// export async function addIdeation({ | ||
// teamId, | ||
// title, | ||
// description, | ||
// vision, | ||
// }: AddIdeationProps): Promise<AsyncActionResponse<AddIdeationResponse>> { | ||
// const token = getAccessToken(); | ||
|
||
// const addIdeationAsync = () => | ||
// POST<AddIdeationBody, AddIdeationResponse>( | ||
// `api/v1/voyages/teams/${teamId}/ideations`, | ||
// token, | ||
// "default", | ||
// { title, description, vision }, | ||
// ); | ||
|
||
// const [res, error] = await handleAsync(addIdeationAsync); | ||
|
||
// if (res) { | ||
// revalidateTag(CacheTag.ideation); | ||
// } | ||
|
||
// return [res, error]; | ||
// } | ||
|
||
export async function editIdeation({ | ||
ideationId, | ||
title, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { container } from "tsyringe"; | ||
|
||
import { TYPES } from "./types"; | ||
import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; | ||
import { IdeationApiAdapter } from "@/modules/ideation/adapters/secondary/ideationApiAdapter"; | ||
import { type RestApiPort } from "@/modules/restApi/ports/secondary/restApiPort"; | ||
import { NextJsRestApiAdapter } from "@/modules/restApi/adapters/secondary/nextJsRestApiAdapter"; | ||
|
||
container.register<IdeationApiPort>(TYPES.IdeationApiPort, { | ||
useClass: IdeationApiAdapter, | ||
}); | ||
|
||
container.register<RestApiPort>(TYPES.RestApiPort, { | ||
useValue: new NextJsRestApiAdapter(process.env.NEXT_PUBLIC_API_URL!), | ||
}); | ||
|
||
export default container; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { container } from "tsyringe"; | ||
import { TYPES } from "./types"; | ||
import type { AddIdeationUseCase } from "@/modules/ideation/application/usecases/addIdeationUseCase"; | ||
import type { AddIdeationVoteUseCase } from "@/modules/ideation/application/usecases/addIdeationVoteUseCase"; | ||
import type { DeleteIdeationUseCase } from "@/modules/ideation/application/usecases/deleteIdeationUseCase"; | ||
import type { EditIdeationUseCase } from "@/modules/ideation/application/usecases/editIdeationUseCase"; | ||
import type { FinalizeIdeationUseCase } from "@/modules/ideation/application/usecases/finalizeIdeationUseCase"; | ||
import type { RemoveIdeationVoteUseCase } from "@/modules/ideation/application/usecases/removeIdeationVoteUseCase"; | ||
|
||
export const injectables = { | ||
/* Ideation */ | ||
addIdeationUseCase: container.resolve<AddIdeationUseCase>( | ||
TYPES.AddIdeationUseCase, | ||
), | ||
addIdeationVoteUseCase: container.resolve<AddIdeationVoteUseCase>( | ||
TYPES.AddIdeationVoteUseCase, | ||
), | ||
deleteIdeationUseCase: container.resolve<DeleteIdeationUseCase>( | ||
TYPES.DeleteIdeationUseCase, | ||
), | ||
editIdeationUseCase: container.resolve<EditIdeationUseCase>( | ||
TYPES.EditIdeationUseCase, | ||
), | ||
finalizeIdeationUseCase: container.resolve<FinalizeIdeationUseCase>( | ||
TYPES.FinalizeIdeationUseCase, | ||
), | ||
removeIdeationVoteUseCase: container.resolve<RemoveIdeationVoteUseCase>( | ||
TYPES.RemoveIdeationVoteUseCase, | ||
), | ||
} as const; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import "reflect-metadata"; | ||
|
||
import { injectables } from "./injectables"; | ||
|
||
const resolveInjection = () => ({ | ||
...injectables, | ||
}); | ||
|
||
export default resolveInjection; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { InjectionToken } from "tsyringe"; | ||
|
||
import container from "./config"; | ||
|
||
export const resolve = | ||
<T>(token: InjectionToken<T>) => | ||
// eslint-disable-next-line indent | ||
() => | ||
// eslint-disable-next-line indent | ||
container.resolve(token); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const TYPES = { | ||
/* Ports */ | ||
RestApiPort: Symbol.for("RestApiPort"), | ||
IdeationApiPort: Symbol.for("IdeationApiPort"), | ||
|
||
/* Adapters */ | ||
// NextJsRestApiAdapter: Symbol.for("NextJsRestApiAdapter"), | ||
IdeationApiAdapter: Symbol.for("IdeationApiAdapter"), | ||
|
||
/* UseCases */ | ||
AddIdeationUseCase: Symbol.for("AddIdeationUseCase"), | ||
AddIdeationVoteUseCase: Symbol.for("AddIdeationVoteUseCase"), | ||
DeleteIdeationUseCase: Symbol.for("DeleteIdeationUseCase"), | ||
EditIdeationUseCase: Symbol.for("EditIdeationUseCase"), | ||
FinalizeIdeationUseCase: Symbol.for("FinalizeIdeationUseCase"), | ||
RemoveIdeationVoteUseCase: Symbol.for("RemoveIdeationVoteUseCase"), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import "reflect-metadata"; | ||
|
||
import { useMemo } from "react"; | ||
|
||
import { injectables } from "@/di/injectables"; | ||
|
||
const useInjection = () => { | ||
const injection = useMemo( | ||
() => ({ | ||
...injectables, | ||
}), | ||
[], | ||
); | ||
|
||
return injection; | ||
}; | ||
|
||
export default useInjection; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { inject, injectable } from "tsyringe"; | ||
import { TYPES } from "@/di/types"; | ||
import { type IdeationApiPort } from "@/modules/ideation/ports/secondary/ideationApiPort"; | ||
import { type RestApiPort } from "@/modules/restApi/ports/secondary/restApiPort"; | ||
import type { | ||
AddIdeationBodyDto, | ||
AddIdeationRequestDto, | ||
DeleteIdeationRequestDto, | ||
EditIdeationBodyDto, | ||
EditIdeationRequestDto, | ||
FinalizeIdeationRequestDto, | ||
IdeationVoteRequestDto, | ||
} from "@/modules/ideation/application/dtos/request.dto"; | ||
import type { | ||
EditIdeationResponseDto, | ||
AddIdeationResponseDto, | ||
DeleteIdeationResponseDto, | ||
IdeationVoteResponseDto, | ||
FinalizeIdeationResponseDto, | ||
} from "@/modules/ideation/application/dtos/response.dto"; | ||
import { IdeationUrls } from "@/modules/ideation/application/constants/ideationUrls"; | ||
|
||
@injectable() | ||
export class IdeationApiAdapter implements IdeationApiPort { | ||
constructor( | ||
@inject(TYPES.RestApiPort) | ||
private readonly apiClient: RestApiPort, | ||
) {} | ||
|
||
async addIdeation({ | ||
teamId, | ||
title, | ||
description, | ||
vision, | ||
cache, | ||
token, | ||
}: AddIdeationRequestDto): Promise<AddIdeationResponseDto> { | ||
return await this.apiClient.post< | ||
AddIdeationBodyDto, | ||
AddIdeationResponseDto | ||
>({ | ||
url: `${IdeationUrls.BASE_URL_TEAMS}/${teamId}/${IdeationUrls.SUB_URLS.IDEATIONS}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
payload: { title, description, vision }, | ||
}); | ||
} | ||
|
||
async editIdeation({ | ||
ideationId, | ||
title, | ||
description, | ||
vision, | ||
cache, | ||
token, | ||
}: EditIdeationRequestDto): Promise<EditIdeationResponseDto> { | ||
return await this.apiClient.patch< | ||
EditIdeationBodyDto, | ||
EditIdeationResponseDto | ||
>({ | ||
url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
payload: { title, description, vision }, | ||
}); | ||
} | ||
|
||
async deleteIdeation({ | ||
ideationId, | ||
cache, | ||
token, | ||
}: DeleteIdeationRequestDto): Promise<DeleteIdeationResponseDto> { | ||
return await this.apiClient.delete<DeleteIdeationResponseDto>({ | ||
url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
}); | ||
} | ||
|
||
async addIdeationVote({ | ||
ideationId, | ||
cache, | ||
token, | ||
}: IdeationVoteRequestDto): Promise<IdeationVoteResponseDto> { | ||
return await this.apiClient.post<undefined, IdeationVoteResponseDto>({ | ||
url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.IDEATION_VOTES}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
}); | ||
} | ||
|
||
async removeIdeationVote({ | ||
ideationId, | ||
cache, | ||
token, | ||
}: IdeationVoteRequestDto): Promise<IdeationVoteResponseDto> { | ||
return await this.apiClient.delete<IdeationVoteResponseDto>({ | ||
url: `${IdeationUrls.BASE_URL_IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.IDEATION_VOTES}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
}); | ||
} | ||
|
||
async finalizeIdeation({ | ||
teamId, | ||
ideationId, | ||
cache, | ||
token, | ||
}: FinalizeIdeationRequestDto): Promise<FinalizeIdeationResponseDto> { | ||
return await this.apiClient.post<undefined, FinalizeIdeationResponseDto>({ | ||
url: `${IdeationUrls.BASE_URL_TEAMS}/${teamId}/${IdeationUrls.SUB_URLS.IDEATIONS}/${ideationId}/${IdeationUrls.SUB_URLS.SELECT}`, | ||
options: { | ||
cache, | ||
token, | ||
}, | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const IdeationUrls = { | ||
BASE_URL_IDEATIONS: "api/v1/voyages/ideations", | ||
BASE_URL_TEAMS: "api/v1/voyages/teams", | ||
SUB_URLS: { | ||
IDEATIONS: "ideations", | ||
IDEATION_VOTES: "ideation-votes", | ||
SELECT: "select", | ||
}, | ||
} as const; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { | ||
IdeationBodyDto, | ||
IdeationRequestDto, | ||
} from "@/modules/ideation/application/dtos/common.dto"; | ||
|
||
export interface AddIdeationUsecaseDto | ||
extends Pick<IdeationRequestDto, "teamId">, | ||
IdeationBodyDto {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { type Ideation } from "@/modules/ideation/application/entities/ideation"; | ||
|
||
export type IdeationBodyDto = Pick< | ||
Ideation, | ||
"title" | "description" | "vision" | ||
>; | ||
|
||
export interface IdeationRequestDto { | ||
teamId: number; | ||
ideationId: number; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, do we prefer to keep all the types/interfaces in the same file with the implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really sure what you mean by this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like I mentioned before, I prefer keeping types/interfaces separate from the actual implementation/logic