refactor: dependency injection to use a centralized Container class for managing application state and services

This commit is contained in:
SquidSpirit 2025-10-14 11:53:57 +08:00
parent de2099011b
commit c5fab7d61a
No known key found for this signature in database
GPG Key ID: 9442A714C9F83C51
13 changed files with 166 additions and 81 deletions

View File

@ -5,9 +5,7 @@ declare global {
// interface Error {} // interface Error {}
interface Locals { interface Locals {
authBloc: import('$lib/auth/adapter/presenter/authBloc').AuthBloc; container: import('$lib/container').Container;
postListBloc: import('$lib/post/adapter/presenter/postListBloc').PostListBloc;
postBloc: import('$lib/post/adapter/presenter/postBloc').PostBloc;
} }
// interface PageData {} // interface PageData {}

View File

@ -1,21 +1,8 @@
import { sequence } from '@sveltejs/kit/hooks'; import { sequence } from '@sveltejs/kit/hooks';
import * as Sentry from '@sentry/sveltekit'; import * as Sentry from '@sentry/sveltekit';
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
import type { Handle } from '@sveltejs/kit'; import type { Handle } from '@sveltejs/kit';
import { Environment } from '$lib/environment'; import { Environment } from '$lib/environment';
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl'; import { Container } from '$lib/container';
import { AuthRepositoryImpl } from '$lib/auth/adapter/gateway/authRepositoryImpl';
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
import type { AuthRepository } from '$lib/auth/application/gateway/authRepository';
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
import { AuthBloc } from '$lib/auth/adapter/presenter/authBloc';
import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
Sentry.init({ Sentry.init({
dsn: Environment.SENTRY_DSN, dsn: Environment.SENTRY_DSN,
@ -24,18 +11,8 @@ Sentry.init({
}); });
export const handle: Handle = sequence(Sentry.sentryHandle(), ({ event, resolve }) => { export const handle: Handle = sequence(Sentry.sentryHandle(), ({ event, resolve }) => {
const authApiService: AuthApiService = new AuthApiServiceImpl(event.fetch); const container = new Container(event.fetch);
const authRepository: AuthRepository = new AuthRepositoryImpl(authApiService); event.locals.container = container;
const getCurrentUserUseCase = new GetCurrentUserUseCase(authRepository);
const postApiService: PostApiService = new PostApiServiceImpl(event.fetch);
const postRepository: PostRepository = new PostRepositoryImpl(postApiService);
const getAllPostsUseCase = new GetAllPostsUseCase(postRepository);
const getPostUseCase = new GetPostUseCase(postRepository);
event.locals.authBloc = new AuthBloc(getCurrentUserUseCase);
event.locals.postListBloc = new PostListBloc(getAllPostsUseCase);
event.locals.postBloc = new PostBloc(getPostUseCase);
return resolve(event); return resolve(event);
}); });

View File

@ -12,15 +12,7 @@ export class AuthBloc {
status: StatusType.Idle, status: StatusType.Idle,
}); });
constructor( constructor(private readonly getCurrentUserUseCase: GetCurrentUserUseCase) {}
private readonly getCurrentUserUseCase: GetCurrentUserUseCase,
initialData?: AuthViewModel
) {
this.state.set({
status: StatusType.Idle,
data: initialData,
});
}
get subscribe() { get subscribe() {
return this.state.subscribe; return this.state.subscribe;

View File

@ -0,0 +1,134 @@
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
import { AuthRepositoryImpl } from '$lib/auth/adapter/gateway/authRepositoryImpl';
import { AuthBloc } from '$lib/auth/adapter/presenter/authBloc';
import type { AuthRepository } from '$lib/auth/application/gateway/authRepository';
import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService';
import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl';
import { ImageBloc } from '$lib/image/adapter/presenter/imageBloc';
import type { ImageRepository } from '$lib/image/application/gateway/imageRepository';
import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
import type { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
export class Container {
private useCases: UseCases;
constructor(fetchFn: typeof fetch) {
const apiServices = new ApiServices(fetchFn);
const repositories = new Repositories(apiServices);
this.useCases = new UseCases(repositories);
}
createAuthBloc(): AuthBloc {
return new AuthBloc(this.useCases.getCurrentUserUseCase);
}
createImageBloc(): ImageBloc {
return new ImageBloc(this.useCases.uploadImageUseCase);
}
createPostListBloc(initialData?: readonly PostInfoViewModel[]): PostListBloc {
return new PostListBloc(this.useCases.getAllPostsUseCase, initialData);
}
createPostBloc(initialData?: PostViewModel): PostBloc {
return new PostBloc(this.useCases.getPostUseCase, initialData);
}
}
class ApiServices {
private fetchFn: typeof fetch;
private _authApiService?: AuthApiService;
private _imageApiService?: ImageApiService;
private _postApiService?: PostApiService;
constructor(fetchFn: typeof fetch) {
this.fetchFn = fetchFn;
}
get authApiService(): AuthApiService {
this._authApiService ??= new AuthApiServiceImpl(this.fetchFn);
return this._authApiService;
}
get imageApiService(): ImageApiService {
this._imageApiService ??= new ImageApiServiceImpl(this.fetchFn);
return this._imageApiService;
}
get postApiService(): PostApiService {
this._postApiService ??= new PostApiServiceImpl(this.fetchFn);
return this._postApiService;
}
}
class Repositories {
private apiServices: ApiServices;
private _authRepository?: AuthRepository;
private _imageRepository?: ImageRepository;
private _postRepository?: PostRepository;
constructor(apiServices: ApiServices) {
this.apiServices = apiServices;
}
get authRepository(): AuthRepository {
this._authRepository ??= new AuthRepositoryImpl(this.apiServices.authApiService);
return this._authRepository;
}
get imageRepository(): ImageRepository {
this._imageRepository ??= new ImageRepositoryImpl(this.apiServices.imageApiService);
return this._imageRepository;
}
get postRepository(): PostRepository {
this._postRepository ??= new PostRepositoryImpl(this.apiServices.postApiService);
return this._postRepository;
}
}
class UseCases {
private repositories: Repositories;
private _getCurrentUserUseCase?: GetCurrentUserUseCase;
private _uploadImageUseCase?: UploadImageUseCase;
private _getAllPostsUseCase?: GetAllPostsUseCase;
private _getPostUseCase?: GetPostUseCase;
constructor(repositories: Repositories) {
this.repositories = repositories;
}
get getCurrentUserUseCase(): GetCurrentUserUseCase {
this._getCurrentUserUseCase ??= new GetCurrentUserUseCase(this.repositories.authRepository);
return this._getCurrentUserUseCase;
}
get uploadImageUseCase(): UploadImageUseCase {
this._uploadImageUseCase ??= new UploadImageUseCase(this.repositories.imageRepository);
return this._uploadImageUseCase;
}
get getAllPostsUseCase(): GetAllPostsUseCase {
this._getAllPostsUseCase ??= new GetAllPostsUseCase(this.repositories.postRepository);
return this._getAllPostsUseCase;
}
get getPostUseCase(): GetPostUseCase {
this._getPostUseCase ??= new GetPostUseCase(this.repositories.postRepository);
return this._getPostUseCase;
}
}

View File

@ -1,5 +1,5 @@
<script> <script>
import MottoAnimatedMark from './MottoAnimatedMark.svelte'; import MottoAnimatedMark from '$lib/home/framework/ui/MottoAnimatedMark.svelte';
</script> </script>
<div <div

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import UploadImageDialoag from './UploadImageDialoag.svelte'; import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte';
import { ImageBloc, ImageEventType } from '$lib/image/adapter/presenter/imageBloc'; import { ImageBloc, ImageEventType } from '$lib/image/adapter/presenter/imageBloc';
import { StatusType } from '$lib/common/adapter/presenter/asyncState'; import { StatusType } from '$lib/common/adapter/presenter/asyncState';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';

View File

@ -57,7 +57,7 @@
<DialogTitle>Upload Image</DialogTitle> <DialogTitle>Upload Image</DialogTitle>
</DialogHeader> </DialogHeader>
<form id="upload-form" onsubmit={onSubmit}> <form id="upload-image-form" onsubmit={onSubmit}>
<Label for="file-input" class="pb-2"> <Label for="file-input" class="pb-2">
{`Image File (${imageMimeTypes.join(', ')})`} {`Image File (${imageMimeTypes.join(', ')})`}
</Label> </Label>
@ -77,7 +77,7 @@
<DialogFooter class="mt-6"> <DialogFooter class="mt-6">
<Button variant="outline" onclick={close} {disabled}>Cancel</Button> <Button variant="outline" onclick={close} {disabled}>Cancel</Button>
<Button type="submit" form="upload-form" {disabled}>Submit</Button> <Button type="submit" form="upload-image-form" {disabled}>Submit</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -5,6 +5,11 @@
import '../app.css'; import '../app.css';
import '@fortawesome/fontawesome-free/css/all.min.css'; import '@fortawesome/fontawesome-free/css/all.min.css';
import { Toaster } from '$lib/common/framework/components/ui/sonner'; import { Toaster } from '$lib/common/framework/components/ui/sonner';
import { Container } from '$lib/container';
import { setContext } from 'svelte';
const container = new Container(fetch);
setContext(Container.name, container);
</script> </script>
<GoogleAnalytics /> <GoogleAnalytics />

View File

@ -1,24 +1,17 @@
<script lang="ts"> <script lang="ts">
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
import { AuthRepositoryImpl } from '$lib/auth/adapter/gateway/authRepositoryImpl';
import { AuthBloc, AuthEventType } from '$lib/auth/adapter/presenter/authBloc'; import { AuthBloc, AuthEventType } from '$lib/auth/adapter/presenter/authBloc';
import type { AuthRepository } from '$lib/auth/application/gateway/authRepository'; import { getContext, onMount, setContext } from 'svelte';
import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
import { onMount, setContext } from 'svelte';
import type { LayoutProps } from './$types'; import type { LayoutProps } from './$types';
import { StatusType } from '$lib/common/adapter/presenter/asyncState'; import { StatusType } from '$lib/common/adapter/presenter/asyncState';
import ErrorPage from '$lib/common/framework/ui/ErrorPage.svelte'; import ErrorPage from '$lib/common/framework/ui/ErrorPage.svelte';
import DashboardNavbar from '$lib/dashboard/framework/ui/DashboardNavbar.svelte'; import DashboardNavbar from '$lib/dashboard/framework/ui/DashboardNavbar.svelte';
import type { DashboardLink } from '$lib/dashboard/framework/ui/dashboardLink'; import type { DashboardLink } from '$lib/dashboard/framework/ui/dashboardLink';
import { Container } from '$lib/container';
const { children }: LayoutProps = $props(); const { children }: LayoutProps = $props();
const container = getContext<Container>(Container.name);
const authApiService: AuthApiService = new AuthApiServiceImpl(fetch); const authBloc = container.createAuthBloc();
const authRepository: AuthRepository = new AuthRepositoryImpl(authApiService);
const getcurrentUserUseCase = new GetCurrentUserUseCase(authRepository);
const authBloc = new AuthBloc(getcurrentUserUseCase);
setContext(AuthBloc.name, authBloc); setContext(AuthBloc.name, authBloc);
onMount(() => authBloc.dispatch({ event: AuthEventType.CurrentUserLoadedEvent })); onMount(() => authBloc.dispatch({ event: AuthEventType.CurrentUserLoadedEvent }));
@ -49,7 +42,7 @@
{:else if hasError} {:else if hasError}
<ErrorPage /> <ErrorPage />
{:else} {:else}
<div class="grid min-h-content-height grid-cols-[auto_1fr]"> <div class="min-h-content-height grid grid-cols-[auto_1fr]">
<DashboardNavbar {links} /> <DashboardNavbar {links} />
{@render children()} {@render children()}
</div> </div>

View File

@ -2,7 +2,8 @@ import { PostListEventType } from '$lib/post/adapter/presenter/postListBloc';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
const { postListBloc } = locals; const { container } = locals;
const postListBloc = container.createPostListBloc();
const state = await postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent }); const state = await postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent });

View File

@ -1,24 +1,16 @@
<script lang="ts"> <script lang="ts">
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc'; import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase'; import { getContext, setContext } from 'svelte';
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
import { setContext } from 'svelte';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel'; import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
import PostOverallPage from '$lib/post/framework/ui/PostOverallPage.svelte'; import PostOverallPage from '$lib/post/framework/ui/PostOverallPage.svelte';
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService'; import { Container } from '$lib/container';
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
let { data }: PageProps = $props(); const { data }: PageProps = $props();
const container = getContext<Container>(Container.name);
const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post)); const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post));
const postListBloc = container.createPostListBloc(initialData);
const postApiService: PostApiService = new PostApiServiceImpl(fetch);
const postRepository: PostRepository = new PostRepositoryImpl(postApiService);
const getAllPostsUseCase = new GetAllPostsUseCase(postRepository);
const postListBloc = new PostListBloc(getAllPostsUseCase, initialData);
setContext(PostListBloc.name, postListBloc); setContext(PostListBloc.name, postListBloc);
</script> </script>

View File

@ -3,7 +3,8 @@ import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals, params }) => { export const load: PageServerLoad = async ({ locals, params }) => {
const { postBloc } = locals; const { container } = locals;
const postBloc = container.createPostBloc();
const state = await postBloc.dispatch({ const state = await postBloc.dispatch({
event: PostEventType.PostLoadedEvent, event: PostEventType.PostLoadedEvent,

View File

@ -1,25 +1,17 @@
<script lang="ts"> <script lang="ts">
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
import { PostBloc } from '$lib/post/adapter/presenter/postBloc'; import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel'; import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase'; import { getContext, setContext } from 'svelte';
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
import { setContext } from 'svelte';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte'; import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte';
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService'; import { Container } from '$lib/container';
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
const { data, params }: PageProps = $props(); const { data, params }: PageProps = $props();
const { id } = params; const { id } = params;
const container = getContext<Container>(Container.name);
const initialData = PostViewModel.rehydrate(data.dehydratedData!); const initialData = PostViewModel.rehydrate(data.dehydratedData!);
const postBloc = container.createPostBloc(initialData);
const postApiService: PostApiService = new PostApiServiceImpl(fetch);
const postRepository: PostRepository = new PostRepositoryImpl(postApiService);
const getPostUseCase = new GetPostUseCase(postRepository);
const postBloc = new PostBloc(getPostUseCase, initialData);
setContext(PostBloc.name, postBloc); setContext(PostBloc.name, postBloc);
</script> </script>