-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuilder.go
More file actions
360 lines (316 loc) · 10.2 KB
/
builder.go
File metadata and controls
360 lines (316 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package modular
import (
"context"
"fmt"
"time"
cloudevents "github.com/cloudevents/sdk-go/v2"
)
// Option represents a functional option for configuring applications
type Option func(*ApplicationBuilder) error
// ApplicationBuilder helps construct applications with various decorators and options
type ApplicationBuilder struct {
baseApp Application
logger Logger
configProvider ConfigProvider
modules []Module
configDecorators []ConfigDecorator
observers []ObserverFunc
tenantLoader TenantLoader
enableObserver bool
enableTenant bool
configLoadedHooks []func(Application) error // Hooks to run after config loading
tenantGuard *StandardTenantGuard
tenantGuardConfig *TenantGuardConfig
dependencyHints []DependencyEdge
drainTimeout time.Duration
parallelInit bool
dynamicReload bool
plugins []Plugin
}
// ObserverFunc is a functional observer that can be registered with the application
type ObserverFunc func(ctx context.Context, event cloudevents.Event) error
// NewApplication creates a new application with the provided options.
// This is the main entry point for the new builder API.
func NewApplication(opts ...Option) (Application, error) {
builder := &ApplicationBuilder{
modules: make([]Module, 0),
configDecorators: make([]ConfigDecorator, 0),
observers: make([]ObserverFunc, 0),
configLoadedHooks: make([]func(Application) error, 0),
}
// Apply all options
for _, opt := range opts {
if err := opt(builder); err != nil {
return nil, err
}
}
// Build the application
return builder.Build()
}
// Build constructs the final application with all decorators applied
func (b *ApplicationBuilder) Build() (Application, error) {
var app Application
// Start with base application or create default
if b.baseApp != nil {
app = b.baseApp
} else {
// Create default config provider if none specified
if b.configProvider == nil {
b.configProvider = NewStdConfigProvider(&struct{}{})
}
// Create default logger if none specified
if b.logger == nil {
return nil, ErrLoggerNotSet
}
// Create base application
if b.enableObserver {
app = NewObservableApplication(b.configProvider, b.logger)
} else {
app = NewStdApplication(b.configProvider, b.logger)
}
}
// Apply config decorators to the base config provider
if len(b.configDecorators) > 0 {
decoratedProvider := b.configProvider
for _, decorator := range b.configDecorators {
decoratedProvider = decorator.DecorateConfig(decoratedProvider)
}
// Update the application's config provider if possible
if baseApp, ok := app.(*StdApplication); ok {
baseApp.cfgProvider = decoratedProvider
} else if obsApp, ok := app.(*ObservableApplication); ok {
obsApp.cfgProvider = decoratedProvider
}
}
// Apply decorators
if b.enableTenant && b.tenantLoader != nil {
app = NewTenantAwareDecorator(app, b.tenantLoader)
}
if b.enableObserver && len(b.observers) > 0 {
app = NewObservableDecorator(app, b.observers...)
}
// Create and register tenant guard if configured.
// Use RegisterService so that the EnhancedServiceRegistry (if enabled) tracks
// the entry and subsequent RegisterService calls don't overwrite it.
if b.tenantGuardConfig != nil {
b.tenantGuard = NewStandardTenantGuard(*b.tenantGuardConfig)
if err := app.RegisterService("tenant.guard", b.tenantGuard); err != nil {
return nil, fmt.Errorf("failed to register tenant guard service: %w", err)
}
}
// Unwrap decorators to find the underlying StdApplication.
baseApp := app
for {
if dec, ok := baseApp.(ApplicationDecorator); ok {
if inner := dec.GetInnerApplication(); inner != nil {
baseApp = inner
continue
}
}
break
}
// Propagate config-driven dependency hints
if len(b.dependencyHints) > 0 {
if stdApp, ok := baseApp.(*StdApplication); ok {
stdApp.dependencyHints = b.dependencyHints
} else if obsApp, ok := baseApp.(*ObservableApplication); ok {
obsApp.dependencyHints = b.dependencyHints
}
}
// Propagate drain timeout
if b.drainTimeout > 0 {
if stdApp, ok := baseApp.(*StdApplication); ok {
stdApp.drainTimeout = b.drainTimeout
} else if obsApp, ok := baseApp.(*ObservableApplication); ok {
obsApp.drainTimeout = b.drainTimeout
}
}
// Propagate dynamic reload
if b.dynamicReload {
if stdApp, ok := baseApp.(*StdApplication); ok {
stdApp.dynamicReload = true
} else if obsApp, ok := baseApp.(*ObservableApplication); ok {
obsApp.dynamicReload = true
}
}
// Propagate parallel init
if b.parallelInit {
if stdApp, ok := baseApp.(*StdApplication); ok {
stdApp.parallelInit = true
} else if obsApp, ok := baseApp.(*ObservableApplication); ok {
obsApp.parallelInit = true
}
}
// Process plugins
for _, plugin := range b.plugins {
for _, mod := range plugin.Modules() {
app.RegisterModule(mod)
}
if withSvc, ok := plugin.(PluginWithServices); ok {
for _, svcDef := range withSvc.Services() {
if err := app.RegisterService(svcDef.Name, svcDef.Service); err != nil {
return nil, fmt.Errorf("plugin %q service %q: %w", plugin.Name(), svcDef.Name, err)
}
}
}
if withHooks, ok := plugin.(PluginWithHooks); ok {
for _, hook := range withHooks.InitHooks() {
app.OnConfigLoaded(hook)
}
}
}
// Register modules
for _, module := range b.modules {
app.RegisterModule(module)
}
// Register config loaded hooks
for _, hook := range b.configLoadedHooks {
app.OnConfigLoaded(hook)
}
return app, nil
}
// WithBaseApplication sets the base application to decorate
func WithBaseApplication(base Application) Option {
return func(b *ApplicationBuilder) error {
b.baseApp = base
return nil
}
}
// WithLogger sets the logger for the application
func WithLogger(logger Logger) Option {
return func(b *ApplicationBuilder) error {
b.logger = logger
return nil
}
}
// WithConfigProvider sets the configuration provider
func WithConfigProvider(provider ConfigProvider) Option {
return func(b *ApplicationBuilder) error {
b.configProvider = provider
return nil
}
}
// WithModules adds modules to the application
func WithModules(modules ...Module) Option {
return func(b *ApplicationBuilder) error {
b.modules = append(b.modules, modules...)
return nil
}
}
// WithModuleDependency declares that module `from` depends on module `to`,
// injecting an edge into the dependency graph before resolution.
func WithModuleDependency(from, to string) Option {
return func(b *ApplicationBuilder) error {
b.dependencyHints = append(b.dependencyHints, DependencyEdge{
From: from,
To: to,
Type: EdgeTypeModule,
})
return nil
}
}
// WithDrainTimeout sets the timeout for the pre-stop drain phase during shutdown.
func WithDrainTimeout(d time.Duration) Option {
return func(b *ApplicationBuilder) error {
b.drainTimeout = d
return nil
}
}
// WithParallelInit enables concurrent module initialization at the same topological depth.
func WithParallelInit() Option {
return func(b *ApplicationBuilder) error {
b.parallelInit = true
return nil
}
}
// WithDynamicReload enables the ReloadOrchestrator, which coordinates
// configuration reloading across all registered Reloadable modules.
func WithDynamicReload() Option {
return func(b *ApplicationBuilder) error {
b.dynamicReload = true
return nil
}
}
// WithPlugins adds plugins to the application. Each plugin's modules, services,
// and init hooks are registered during Build().
func WithPlugins(plugins ...Plugin) Option {
return func(b *ApplicationBuilder) error {
b.plugins = append(b.plugins, plugins...)
return nil
}
}
// WithConfigDecorators adds configuration decorators
func WithConfigDecorators(decorators ...ConfigDecorator) Option {
return func(b *ApplicationBuilder) error {
b.configDecorators = append(b.configDecorators, decorators...)
return nil
}
}
// WithObserver enables observer pattern and adds observer functions
func WithObserver(observers ...ObserverFunc) Option {
return func(b *ApplicationBuilder) error {
b.enableObserver = true
b.observers = append(b.observers, observers...)
return nil
}
}
// WithTenantAware enables tenant-aware functionality with the provided loader
func WithTenantAware(loader TenantLoader) Option {
return func(b *ApplicationBuilder) error {
b.enableTenant = true
b.tenantLoader = loader
return nil
}
}
// WithOnConfigLoaded registers hooks to run after config loading but before module initialization.
// This is useful for reconfiguring dependencies (logger, metrics, tracing) based on loaded config.
// Multiple hooks can be registered and will be executed in registration order.
//
// Example:
//
// app, err := modular.NewApplication(
// modular.WithLogger(defaultLogger),
// modular.WithConfigProvider(configProvider),
// modular.WithOnConfigLoaded(func(app modular.Application) error {
// config := app.ConfigProvider().GetConfig().(*AppConfig)
// if config.LogFormat == "json" {
// newLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// app.SetLogger(newLogger)
// }
// return nil
// }),
// modular.WithModules(modules...),
// )
func WithOnConfigLoaded(hooks ...func(Application) error) Option {
return func(b *ApplicationBuilder) error {
b.configLoadedHooks = append(b.configLoadedHooks, hooks...)
return nil
}
}
// WithTenantGuardMode enables the tenant guard with the specified mode using default config.
func WithTenantGuardMode(mode TenantGuardMode) Option {
return func(b *ApplicationBuilder) error {
if b.tenantGuardConfig == nil {
cfg := DefaultTenantGuardConfig()
b.tenantGuardConfig = &cfg
}
b.tenantGuardConfig.Mode = mode
return nil
}
}
// WithTenantGuardConfig enables the tenant guard with a full configuration.
func WithTenantGuardConfig(config TenantGuardConfig) Option {
return func(b *ApplicationBuilder) error {
b.tenantGuardConfig = &config
return nil
}
}
// Convenience functions for creating common decorators
// InstanceAwareConfig creates an instance-aware configuration decorator
func InstanceAwareConfig() ConfigDecorator {
return &instanceAwareConfigDecorator{}
}
// TenantAwareConfigDecorator creates a tenant-aware configuration decorator
func TenantAwareConfigDecorator(loader TenantLoader) ConfigDecorator {
return &tenantAwareConfigDecorator{loader: loader}
}