Skip to content

Commit 8f55afb

Browse files
committed
feat(api): create singleton for app dependency management
Introduces `AppDependencies`, a singleton class to manage the lifecycle of all application services and repositories. This class uses a lazy initialization pattern, creating and configuring all dependencies only on the first request. It leverages the `DatabaseConnectionManager` and includes detailed logging to trace the initialization process, ensuring a robust and performant startup sequence.
1 parent 41d0726 commit 8f55afb

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

lib/src/config/app_dependencies.dart

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import 'dart:async';
2+
3+
import 'package:ht_api/src/config/database_connection.dart';
4+
import 'package:ht_api/src/rbac/permission_service.dart';
5+
import 'package:ht_api/src/services/auth_service.dart';
6+
import 'package:ht_api/src/services/auth_token_service.dart';
7+
import 'package:ht_api/src/services/dashboard_summary_service.dart';
8+
import 'package:ht_api/src/services/database_seeding_service.dart';
9+
import 'package:ht_api/src/services/default_user_preference_limit_service.dart';
10+
import 'package:ht_api/src/services/jwt_auth_token_service.dart';
11+
import 'package:ht_api/src/services/token_blacklist_service.dart';
12+
import 'package:ht_api/src/services/user_preference_limit_service.dart';
13+
import 'package:ht_api/src/services/verification_code_storage_service.dart';
14+
import 'package:ht_data_client/ht_data_client.dart';
15+
import 'package:ht_data_postgres/ht_data_postgres.dart';
16+
import 'package:ht_data_repository/ht_data_repository.dart';
17+
import 'package:ht_email_inmemory/ht_email_inmemory.dart';
18+
import 'package:ht_email_repository/ht_email_repository.dart';
19+
import 'package:ht_shared/ht_shared.dart';
20+
import 'package:logging/logging.dart';
21+
import 'package:postgres/postgres.dart';
22+
import 'package:uuid/uuid.dart';
23+
24+
/// A singleton class to manage all application dependencies.
25+
///
26+
/// This class follows a lazy initialization pattern. Dependencies are created
27+
/// only when the `init()` method is first called, typically triggered by the
28+
/// first incoming request. A `Completer` ensures that subsequent requests
29+
/// await the completion of the initial setup.
30+
class AppDependencies {
31+
AppDependencies._();
32+
33+
/// The single, global instance of the [AppDependencies].
34+
static final instance = AppDependencies._();
35+
36+
final _log = Logger('AppDependencies');
37+
final _completer = Completer<void>();
38+
39+
// --- Repositories ---
40+
late final HtDataRepository<Headline> headlineRepository;
41+
late final HtDataRepository<Category> categoryRepository;
42+
late final HtDataRepository<Source> sourceRepository;
43+
late final HtDataRepository<Country> countryRepository;
44+
late final HtDataRepository<User> userRepository;
45+
late final HtDataRepository<UserAppSettings> userAppSettingsRepository;
46+
late final HtDataRepository<UserContentPreferences>
47+
userContentPreferencesRepository;
48+
late final HtDataRepository<AppConfig> appConfigRepository;
49+
50+
// --- Services ---
51+
late final HtEmailRepository emailRepository;
52+
late final TokenBlacklistService tokenBlacklistService;
53+
late final AuthTokenService authTokenService;
54+
late final VerificationCodeStorageService verificationCodeStorageService;
55+
late final AuthService authService;
56+
late final DashboardSummaryService dashboardSummaryService;
57+
late final PermissionService permissionService;
58+
late final UserPreferenceLimitService userPreferenceLimitService;
59+
60+
/// Initializes all application dependencies.
61+
///
62+
/// This method is idempotent. It performs the full initialization only on
63+
/// the first call. Subsequent calls will await the result of the first one.
64+
Future<void> init() {
65+
if (_completer.isCompleted) {
66+
_log.fine('Dependencies already initializing/initialized.');
67+
return _completer.future;
68+
}
69+
70+
_log.info('Initializing application dependencies...');
71+
_init()
72+
.then((_) {
73+
_log.info('Application dependencies initialized successfully.');
74+
_completer.complete();
75+
})
76+
.catchError((Object e, StackTrace s) {
77+
_log.severe('Failed to initialize application dependencies.', e, s);
78+
_completer.completeError(e, s);
79+
});
80+
81+
return _completer.future;
82+
}
83+
84+
Future<void> _init() async {
85+
// 1. Establish Database Connection.
86+
await DatabaseConnectionManager.instance.init();
87+
final connection = await DatabaseConnectionManager.instance.connection;
88+
89+
// 2. Run Database Seeding.
90+
final seedingService = DatabaseSeedingService(
91+
connection: connection,
92+
log: _log,
93+
);
94+
await seedingService.createTables();
95+
await seedingService.seedGlobalFixtureData();
96+
await seedingService.seedInitialAdminAndConfig();
97+
98+
// 3. Initialize Repositories.
99+
headlineRepository = _createRepository(
100+
connection,
101+
'headlines',
102+
Headline.fromJson,
103+
(h) => h.toJson(),
104+
);
105+
categoryRepository = _createRepository(
106+
connection,
107+
'categories',
108+
Category.fromJson,
109+
(c) => c.toJson(),
110+
);
111+
sourceRepository = _createRepository(
112+
connection,
113+
'sources',
114+
Source.fromJson,
115+
(s) => s.toJson(),
116+
);
117+
countryRepository = _createRepository(
118+
connection,
119+
'countries',
120+
Country.fromJson,
121+
(c) => c.toJson(),
122+
);
123+
userRepository = _createRepository(
124+
connection,
125+
'users',
126+
User.fromJson,
127+
(u) => u.toJson(),
128+
);
129+
userAppSettingsRepository = _createRepository(
130+
connection,
131+
'user_app_settings',
132+
UserAppSettings.fromJson,
133+
(s) => s.toJson(),
134+
);
135+
userContentPreferencesRepository = _createRepository(
136+
connection,
137+
'user_content_preferences',
138+
UserContentPreferences.fromJson,
139+
(p) => p.toJson(),
140+
);
141+
appConfigRepository = _createRepository(
142+
connection,
143+
'app_config',
144+
AppConfig.fromJson,
145+
(c) => c.toJson(),
146+
);
147+
148+
// 4. Initialize Services.
149+
emailRepository = const HtEmailRepository(
150+
emailClient: HtEmailInMemoryClient(),
151+
);
152+
tokenBlacklistService = InMemoryTokenBlacklistService();
153+
authTokenService = JwtAuthTokenService(
154+
userRepository: userRepository,
155+
blacklistService: tokenBlacklistService,
156+
uuidGenerator: const Uuid(),
157+
);
158+
verificationCodeStorageService = InMemoryVerificationCodeStorageService();
159+
authService = AuthService(
160+
userRepository: userRepository,
161+
authTokenService: authTokenService,
162+
verificationCodeStorageService: verificationCodeStorageService,
163+
emailRepository: emailRepository,
164+
userAppSettingsRepository: userAppSettingsRepository,
165+
userContentPreferencesRepository: userContentPreferencesRepository,
166+
uuidGenerator: const Uuid(),
167+
);
168+
dashboardSummaryService = DashboardSummaryService(
169+
headlineRepository: headlineRepository,
170+
categoryRepository: categoryRepository,
171+
sourceRepository: sourceRepository,
172+
);
173+
permissionService = const PermissionService();
174+
userPreferenceLimitService = DefaultUserPreferenceLimitService(
175+
appConfigRepository: appConfigRepository,
176+
);
177+
}
178+
179+
HtDataRepository<T> _createRepository<T>(
180+
Connection connection,
181+
String tableName,
182+
FromJson<T> fromJson,
183+
ToJson<T> toJson,
184+
) {
185+
return HtDataRepository<T>(
186+
dataClient: HtDataPostgresClient<T>(
187+
connection: connection,
188+
tableName: tableName,
189+
fromJson: fromJson,
190+
toJson: toJson,
191+
log: _log,
192+
),
193+
);
194+
}
195+
}

0 commit comments

Comments
 (0)