From 224d57d8f40f4cb4f8d7397eb73c6e306aaffc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Strzali=C5=84ski?= Date: Tue, 17 Dec 2024 16:18:07 +0100 Subject: [PATCH] Dependency injection improvements (#1118) This PR: - simplifies the way we inject dependencies. There is only one interface to inject `Dependencies` - dependency injection is based on the component tree - a component tree is built using the dedicated interface `ChildComponentProvider` --- .../basic_http_frontend_connector.go | 24 +++- quesma/frontend_connectors/router_v2.go | 14 +- quesma/quesma/dual_write_proxy_v2.go | 6 +- .../quesma/elastic_http_frontend_connector.go | 38 +++-- quesma/quesma/quesma.go | 6 +- quesma/quesma/router_v2.go | 12 +- quesma/v2/core/dependency_injection.go | 130 +++++++++++++++--- quesma/v2/core/dependency_injection_test.go | 15 +- quesma/v2/core/diag/diagnostic.go | 30 ---- quesma/v2/core/diag/empty.go | 7 - quesma/v2/core/quesma_apis.go | 2 +- quesma/v2/core/quesma_builder.go | 63 +++++---- quesma/v2/core/quesma_pipeline.go | 16 +++ 13 files changed, 234 insertions(+), 129 deletions(-) delete mode 100644 quesma/v2/core/diag/diagnostic.go diff --git a/quesma/frontend_connectors/basic_http_frontend_connector.go b/quesma/frontend_connectors/basic_http_frontend_connector.go index 3d2a7174b..49efde0b0 100644 --- a/quesma/frontend_connectors/basic_http_frontend_connector.go +++ b/quesma/frontend_connectors/basic_http_frontend_connector.go @@ -27,17 +27,26 @@ type BasicHTTPFrontendConnector struct { registry schema.Registry config *config.QuesmaConfiguration - diagnostic diag.Diagnostic + phoneHomeClient diag.PhoneHomeClient + debugInfoCollector diag.DebugInfoCollector } -func (h *BasicHTTPFrontendConnector) InjectDiagnostic(diagnostic diag.Diagnostic) { +func (h *BasicHTTPFrontendConnector) GetChildComponents() []interface{} { + components := make([]interface{}, 0) - h.diagnostic = diagnostic + if h.router != nil { + components = append(components, h.router) + } - // TODO this is a hack if h.routerInstance != nil { - h.routerInstance.InjectDiagnostic(diagnostic) + components = append(components, h.routerInstance) } + return components +} + +func (h *BasicHTTPFrontendConnector) SetDependencies(deps quesma_api.Dependencies) { + h.phoneHomeClient = deps.PhoneHomeAgent() + h.debugInfoCollector = deps.DebugInfoCollector() } func NewBasicHTTPFrontendConnector(endpoint string, config *config.QuesmaConfiguration) *BasicHTTPFrontendConnector { @@ -70,9 +79,10 @@ func (h *BasicHTTPFrontendConnector) ServeHTTP(w http.ResponseWriter, req *http. } ua := req.Header.Get("User-Agent") - if h.diagnostic.PhoneHomeAgent() != nil { - h.diagnostic.PhoneHomeAgent().UserAgentCounters().Add(ua, 1) + if h.phoneHomeClient != nil { + h.phoneHomeClient.UserAgentCounters().Add(ua, 1) } + h.routerInstance.Reroute(req.Context(), w, req, reqBody, h.router, h.logManager, h.registry) } diff --git a/quesma/frontend_connectors/router_v2.go b/quesma/frontend_connectors/router_v2.go index 3552b7994..ca6fa761f 100644 --- a/quesma/frontend_connectors/router_v2.go +++ b/quesma/frontend_connectors/router_v2.go @@ -81,11 +81,13 @@ type RouterV2 struct { HttpClient *http.Client FailedRequests atomic.Int64 - diagnostic diag.Diagnostic + debugInfoCollector diag.DebugInfoCollector + phoneHomeAgent diag.PhoneHomeClient } -func (r *RouterV2) InjectDiagnostic(s diag.Diagnostic) { - r.diagnostic = s +func (r *RouterV2) SetDependencies(deps quesma_api.Dependencies) { + r.debugInfoCollector = deps.DebugInfoCollector() + r.phoneHomeAgent = deps.PhoneHomeAgent() } func NewRouterV2(config *config.QuesmaConfiguration) *RouterV2 { tr := &http.Transport{ @@ -267,7 +269,7 @@ func (r *RouterV2) Reroute(ctx context.Context, w http.ResponseWriter, req *http } dispatcher := &quesma_api.Dispatcher{} if handlersPipe != nil { - quesmaResponse, err := recordRequestToClickhouseV2(req.URL.Path, r.diagnostic.DebugInfoCollector(), func() (*quesma_api.Result, error) { + quesmaResponse, err := recordRequestToClickhouseV2(req.URL.Path, r.debugInfoCollector, func() (*quesma_api.Result, error) { var result *quesma_api.Result result, err = handlersPipe.Handler(ctx, quesmaRequest, w) @@ -357,11 +359,11 @@ func (r *RouterV2) sendHttpRequestToElastic(ctx context.Context, req *http.Reque } go func() { - elkResponseChan <- recordRequestToElasticV2(req.URL.Path, r.diagnostic.DebugInfoCollector(), func() elasticResultV2 { + elkResponseChan <- recordRequestToElasticV2(req.URL.Path, r.debugInfoCollector, func() elasticResultV2 { isWrite := elasticsearch.IsWriteRequest(req) - phoneHome := r.diagnostic.PhoneHomeAgent() + phoneHome := r.phoneHomeAgent var span diag.Span if isManagement { diff --git a/quesma/quesma/dual_write_proxy_v2.go b/quesma/quesma/dual_write_proxy_v2.go index 500e95088..e40fb658f 100644 --- a/quesma/quesma/dual_write_proxy_v2.go +++ b/quesma/quesma/dual_write_proxy_v2.go @@ -68,9 +68,9 @@ func (q *dualWriteHttpProxyV2) Stop(ctx context.Context) { q.Close(ctx) } -func newDualWriteProxyV2(dependencies *quesma_api.Dependencies, schemaLoader clickhouse.TableDiscovery, logManager *clickhouse.LogManager, indexManager elasticsearch.IndexManagement, registry schema.Registry, config *config.QuesmaConfiguration, ingestProcessor *ingest.IngestProcessor, resolver table_resolver.TableResolver, abResultsRepository ab_testing.Sender) *dualWriteHttpProxyV2 { +func newDualWriteProxyV2(dependencies quesma_api.Dependencies, schemaLoader clickhouse.TableDiscovery, logManager *clickhouse.LogManager, indexManager elasticsearch.IndexManagement, registry schema.Registry, config *config.QuesmaConfiguration, ingestProcessor *ingest.IngestProcessor, resolver table_resolver.TableResolver, abResultsRepository ab_testing.Sender) *dualWriteHttpProxyV2 { - queryProcessor := NewQueryRunner(logManager, config, indexManager, dependencies.Diagnostic.DebugInfoCollector(), registry, abResultsRepository, resolver, schemaLoader) + queryProcessor := NewQueryRunner(logManager, config, indexManager, dependencies.DebugInfoCollector(), registry, abResultsRepository, resolver, schemaLoader) // not sure how we should configure our query translator ??? // is this a config option?? @@ -82,7 +82,7 @@ func newDualWriteProxyV2(dependencies *quesma_api.Dependencies, schemaLoader cli routerInstance := frontend_connectors.NewRouterV2(config) - dependencies.Diagnostic.PhoneHomeAgent().FailedRequestsCollector(func() int64 { + dependencies.PhoneHomeAgent().FailedRequestsCollector(func() int64 { return routerInstance.FailedRequests.Load() }) diff --git a/quesma/quesma/elastic_http_frontend_connector.go b/quesma/quesma/elastic_http_frontend_connector.go index 30921d502..62f24ef81 100644 --- a/quesma/quesma/elastic_http_frontend_connector.go +++ b/quesma/quesma/elastic_http_frontend_connector.go @@ -16,8 +16,10 @@ import ( type ElasticHttpIngestFrontendConnector struct { *frontend_connectors.BasicHTTPFrontendConnector - Config *config.QuesmaConfiguration - diagnostic diag.Diagnostic + + Config *config.QuesmaConfiguration + + phoneHomeClient diag.PhoneHomeClient } func NewElasticHttpIngestFrontendConnector(endpoint string, @@ -39,15 +41,23 @@ func NewElasticHttpIngestFrontendConnector(endpoint string, return fc } -func (h *ElasticHttpIngestFrontendConnector) InjectDiagnostic(diagnostic diag.Diagnostic) { - h.diagnostic = diagnostic - // TODO this is a hack - h.BasicHTTPFrontendConnector.InjectDiagnostic(diagnostic) +func (h *ElasticHttpIngestFrontendConnector) GetChildComponents() []interface{} { + components := make([]interface{}, 0) + if h.BasicHTTPFrontendConnector != nil { + components = append(components, h.BasicHTTPFrontendConnector) + } + + return components +} + +func (h *ElasticHttpIngestFrontendConnector) SetDependencies(deps quesma_api.Dependencies) { + h.phoneHomeClient = deps.PhoneHomeAgent() } type ElasticHttpQueryFrontendConnector struct { *frontend_connectors.BasicHTTPFrontendConnector - diagnostic diag.Diagnostic + + phoneHomeClient diag.PhoneHomeClient } func NewElasticHttpQueryFrontendConnector(endpoint string, @@ -67,8 +77,14 @@ func NewElasticHttpQueryFrontendConnector(endpoint string, return fc } -func (h *ElasticHttpQueryFrontendConnector) InjectDiagnostic(diagnostic diag.Diagnostic) { - h.diagnostic = diagnostic - // TODO this is a hack - h.BasicHTTPFrontendConnector.InjectDiagnostic(diagnostic) +func (h *ElasticHttpQueryFrontendConnector) GetChildComponents() []interface{} { + components := make([]interface{}, 0) + if h.BasicHTTPFrontendConnector != nil { + components = append(components, h.BasicHTTPFrontendConnector) + } + return components +} + +func (h *ElasticHttpQueryFrontendConnector) SetDependencies(deps quesma_api.Dependencies) { + h.phoneHomeClient = deps.PhoneHomeAgent() } diff --git a/quesma/quesma/quesma.go b/quesma/quesma/quesma.go index ca8915d50..27e10b4e9 100644 --- a/quesma/quesma/quesma.go +++ b/quesma/quesma/quesma.go @@ -18,7 +18,6 @@ import ( "quesma/telemetry" "quesma/util" quesma_v2 "quesma_v2/core" - "quesma_v2/core/diag" ) type ( @@ -65,10 +64,9 @@ func NewHttpProxy(phoneHomeAgent telemetry.PhoneHomeAgent, abResultsRepository ab_testing.Sender, resolver table_resolver.TableResolver, v2 bool) *Quesma { - statistics := diag.NewStatistics(phoneHomeAgent, quesmaManagementConsole) - dependencies := quesma_v2.NewDependencies() - dependencies.Diagnostic = statistics + dependencies.SetPhoneHomeAgent(phoneHomeAgent) + dependencies.SetDebugInfoCollector(quesmaManagementConsole) if v2 { return &Quesma{ diff --git a/quesma/quesma/router_v2.go b/quesma/quesma/router_v2.go index 271bc761d..5369c0d3f 100644 --- a/quesma/quesma/router_v2.go +++ b/quesma/quesma/router_v2.go @@ -31,7 +31,7 @@ import ( "time" ) -func ConfigureIngestRouterV2(cfg *config.QuesmaConfiguration, dependencies *quesma_api.Dependencies, ip *ingest.IngestProcessor, tableResolver table_resolver.TableResolver) quesma_api.Router { +func ConfigureIngestRouterV2(cfg *config.QuesmaConfiguration, dependencies quesma_api.Dependencies, ip *ingest.IngestProcessor, tableResolver table_resolver.TableResolver) quesma_api.Router { // some syntactic sugar method := quesma_api.IsHTTPMethod and := quesma_api.And @@ -90,7 +90,7 @@ func ConfigureIngestRouterV2(cfg *config.QuesmaConfiguration, dependencies *ques return nil, err } - results, err := bulk.Write(ctx, nil, body, ip, cfg, dependencies.Diagnostic.PhoneHomeAgent(), tableResolver) + results, err := bulk.Write(ctx, nil, body, ip, cfg, dependencies.PhoneHomeAgent(), tableResolver) return bulkInsertResult(ctx, results, err) }) router.Register(routes.IndexDocPath, and(method("POST"), matchedExactIngestPath(tableResolver)), func(ctx context.Context, req *quesma_api.Request, _ http.ResponseWriter) (*quesma_api.Result, error) { @@ -105,7 +105,7 @@ func ConfigureIngestRouterV2(cfg *config.QuesmaConfiguration, dependencies *ques }, nil } - result, err := doc.Write(ctx, &index, body, ip, cfg, dependencies.Diagnostic.PhoneHomeAgent(), tableResolver) + result, err := doc.Write(ctx, &index, body, ip, cfg, dependencies.PhoneHomeAgent(), tableResolver) if err != nil { return &quesma_api.Result{ Body: string(queryparser.BadRequestParseError(err)), @@ -125,13 +125,13 @@ func ConfigureIngestRouterV2(cfg *config.QuesmaConfiguration, dependencies *ques return nil, err } - results, err := bulk.Write(ctx, &index, body, ip, cfg, dependencies.Diagnostic.PhoneHomeAgent(), tableResolver) + results, err := bulk.Write(ctx, &index, body, ip, cfg, dependencies.PhoneHomeAgent(), tableResolver) return bulkInsertResult(ctx, results, err) }) return router } -func ConfigureSearchRouterV2(cfg *config.QuesmaConfiguration, dependencies *quesma_api.Dependencies, sr schema.Registry, lm *clickhouse.LogManager, queryRunner *QueryRunner, tableResolver table_resolver.TableResolver) quesma_api.Router { +func ConfigureSearchRouterV2(cfg *config.QuesmaConfiguration, dependencies quesma_api.Dependencies, sr schema.Registry, lm *clickhouse.LogManager, queryRunner *QueryRunner, tableResolver table_resolver.TableResolver) quesma_api.Router { // some syntactic sugar method := quesma_api.IsHTTPMethod @@ -366,7 +366,7 @@ func ConfigureSearchRouterV2(cfg *config.QuesmaConfiguration, dependencies *ques return nil, errors.New("invalid request body, expecting JSON") } - if responseBody, err := terms_enum.HandleTermsEnum(ctx, req.Params["index"], body, lm, sr, dependencies.Diagnostic.DebugInfoCollector()); err != nil { + if responseBody, err := terms_enum.HandleTermsEnum(ctx, req.Params["index"], body, lm, sr, dependencies.DebugInfoCollector()); err != nil { return nil, err } else { return elasticsearchQueryResult(string(responseBody), http.StatusOK), nil diff --git a/quesma/v2/core/dependency_injection.go b/quesma/v2/core/dependency_injection.go index f49ee2ea7..a7846b8c9 100644 --- a/quesma/v2/core/dependency_injection.go +++ b/quesma/v2/core/dependency_injection.go @@ -7,53 +7,139 @@ import ( "quesma_v2/core/diag" ) +type Dependencies interface { + PhoneHomeAgent() diag.PhoneHomeClient + DebugInfoCollector() diag.DebugInfoCollector + + InjectDependenciesInto(a any) +} + // Here are interfaces that are used to inject dependencies into structs. // Component that require a dependency should implement the corresponding interface. // -type DiagnosticInjector interface { - InjectDiagnostic(s diag.Diagnostic) +type DependenciesSetter interface { + SetDependencies(deps Dependencies) +} + +// + +type ChildComponentProvider interface { + GetChildComponents() []any +} + +type ComponentTreeNode struct { + Id string + Level int + Component any + Children []*ComponentTreeNode +} + +func (n *ComponentTreeNode) walk(f func(*ComponentTreeNode)) { + f(n) + for _, child := range n.Children { + child.walk(f) + } +} + +type ComponentTreeBuilder struct { + visited map[any]*ComponentTreeNode +} + +func NewComponentToInitializeProviderBuilder() *ComponentTreeBuilder { + return &ComponentTreeBuilder{ + visited: make(map[any]*ComponentTreeNode), + } +} + +func (b *ComponentTreeBuilder) buildComponentTree(level int, a any) *ComponentTreeNode { + + // cycle detection + // TODO add detection if the a is hashable + if v, ok := b.visited[a]; ok { + return v + } + + node := &ComponentTreeNode{ + Id: fmt.Sprintf("%T(%p)", a, a), + Children: make([]*ComponentTreeNode, 0), + Component: a, + Level: level, + } + + b.visited[a] = node + + if provider, ok := a.(ChildComponentProvider); ok { + for _, child := range provider.GetChildComponents() { + childNode := b.buildComponentTree(level+1, child) + node.Children = append(node.Children, childNode) + } + } + + return node +} + +func (b *ComponentTreeBuilder) BuildComponentTree(a any) *ComponentTreeNode { + return b.buildComponentTree(0, a) } // Dependencies is a struct that contains all the dependencies that can be injected during Quesma building. -type Dependencies struct { - Diagnostic diag.Diagnostic +type DependenciesImpl struct { + phoneHomeAgent diag.PhoneHomeClient + debugInfoCollector diag.DebugInfoCollector +} + +func (d *DependenciesImpl) PhoneHomeAgent() diag.PhoneHomeClient { + return d.phoneHomeAgent +} + +func (d *DependenciesImpl) DebugInfoCollector() diag.DebugInfoCollector { + return d.debugInfoCollector } -func NewDependencies() *Dependencies { - return &Dependencies{} +func NewDependencies() *DependenciesImpl { + return EmptyDependencies() } -func EmptyDependencies() *Dependencies { - return &Dependencies{ - Diagnostic: diag.EmptyDiagnostic(), +func (d *DependenciesImpl) SetPhoneHomeAgent(phoneHomeAgent diag.PhoneHomeClient) { + d.phoneHomeAgent = phoneHomeAgent +} + +func (d *DependenciesImpl) SetDebugInfoCollector(debugInfoCollector diag.DebugInfoCollector) { + d.debugInfoCollector = debugInfoCollector +} + +func EmptyDependencies() *DependenciesImpl { + return &DependenciesImpl{ + phoneHomeAgent: diag.NewPhoneHomeEmptyAgent(), + debugInfoCollector: diag.EmptyDebugInfoCollector(), } } const traceDependencyInjection bool = false // InjectDependenciesInto injects dependencies into a component. This is indented to use in Quesma building process only. -func (d *Dependencies) InjectDependenciesInto(a any) { +func (d *DependenciesImpl) InjectDependenciesInto(a any) { // TODO fmt for now. Later we can use logger. We need to move logger to the V2 module. - if traceDependencyInjection { - fmt.Printf("BEGIN - Injecting dependencies into %T. \n", a) - } + var trace func(a ...any) - if injector, ok := a.(DiagnosticInjector); ok { - injector.InjectDiagnostic(d.Diagnostic) - if traceDependencyInjection { - fmt.Printf(" OK - Injected Diagnostic into %T\n", a) + if traceDependencyInjection { + prefix := fmt.Sprintf("Dependency injection into %T :", a) + trace = func(a ...any) { + fmt.Println(prefix, fmt.Sprint(a...)) } } else { - if traceDependencyInjection { - fmt.Printf(" SKIP - No Diagnostic to inject into %T. It doesn't implement DiagnosticInjector interface.\n", a) - } + trace = func(a ...any) {} } - if traceDependencyInjection { - fmt.Printf("END - Injecting dependencies into %T\n", a) + if injector, ok := a.(DependenciesSetter); ok { + injector.SetDependencies(d) + trace("OK - Injected Dependencies") + + } else { + trace("SKIP - No dependencies to inject. It doesn't implement DependenciesSetter interface.") } } diff --git a/quesma/v2/core/dependency_injection_test.go b/quesma/v2/core/dependency_injection_test.go index ff1b090df..9303d9bc2 100644 --- a/quesma/v2/core/dependency_injection_test.go +++ b/quesma/v2/core/dependency_injection_test.go @@ -8,21 +8,20 @@ import ( ) type componentWithDependency struct { - diag diag.Diagnostic + phoneHomeClient diag.PhoneHomeClient } -func (sc *componentWithDependency) InjectDiagnostic(d diag.Diagnostic) { - sc.diag = d +func (sc *componentWithDependency) SetDependencies(deps Dependencies) { + sc.phoneHomeClient = deps.PhoneHomeAgent() } type componentWithoutDependencyInjection struct { - diag diag.Diagnostic + phoneHomeClient diag.PhoneHomeClient } func Test_dependencyInjection(t *testing.T) { - deps := NewDependencies() - deps.Diagnostic = diag.EmptyDiagnostic() + deps := EmptyDependencies() component1 := &componentWithDependency{} component2 := &componentWithoutDependencyInjection{} @@ -30,11 +29,11 @@ func Test_dependencyInjection(t *testing.T) { deps.InjectDependenciesInto(component1) deps.InjectDependenciesInto(component2) - if component1.diag == nil { + if component1.phoneHomeClient == nil { t.Errorf("Expected diagnostic to be injected") } - if component2.diag != nil { + if component2.phoneHomeClient != nil { t.Errorf("Expected diagnostic not to be injected") } diff --git a/quesma/v2/core/diag/diagnostic.go b/quesma/v2/core/diag/diagnostic.go deleted file mode 100644 index b550157a6..000000000 --- a/quesma/v2/core/diag/diagnostic.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Quesma, licensed under the Elastic License 2.0. -// SPDX-License-Identifier: Elastic-2.0 -package diag - -// Diagnostic is an interface that provides access to diagnostic tools. -// Content of this interface may change in the future. -type Diagnostic interface { - PhoneHomeAgent() PhoneHomeClient - DebugInfoCollector() DebugInfoCollector -} - -type diagnosticImpl struct { - phoneHomeAgent PhoneHomeClient - debugInfoCollector DebugInfoCollector -} - -func NewStatistics(phoneHomeAgent PhoneHomeClient, debugInfoCollector DebugInfoCollector) Diagnostic { - return &diagnosticImpl{ - phoneHomeAgent: phoneHomeAgent, - debugInfoCollector: debugInfoCollector, - } -} - -func (s *diagnosticImpl) PhoneHomeAgent() PhoneHomeClient { - return s.phoneHomeAgent -} - -func (s *diagnosticImpl) DebugInfoCollector() DebugInfoCollector { - return s.debugInfoCollector -} diff --git a/quesma/v2/core/diag/empty.go b/quesma/v2/core/diag/empty.go index 86cbbdf1c..5fb10adff 100644 --- a/quesma/v2/core/diag/empty.go +++ b/quesma/v2/core/diag/empty.go @@ -120,10 +120,3 @@ func (e *emptyDebugInfoCollector) PushSecondaryInfo(qdebugInfo *QueryDebugSecond func (e *emptyDebugInfoCollector) RecordRequest(typeName string, took time.Duration, error bool) { } - -func EmptyDiagnostic() Diagnostic { - return &diagnosticImpl{ - phoneHomeAgent: NewPhoneHomeEmptyAgent(), - debugInfoCollector: EmptyDebugInfoCollector(), - } -} diff --git a/quesma/v2/core/quesma_apis.go b/quesma/v2/core/quesma_apis.go index a6f3f1786..806b0e361 100644 --- a/quesma/v2/core/quesma_apis.go +++ b/quesma/v2/core/quesma_apis.go @@ -59,7 +59,7 @@ type PipelineBuilder interface { type QuesmaBuilder interface { AddPipeline(pipeline PipelineBuilder) GetPipelines() []PipelineBuilder - SetDependencies(dependencies *Dependencies) + SetDependencies(dependencies Dependencies) Build() (QuesmaBuilder, error) Start() Stop(ctx context.Context) diff --git a/quesma/v2/core/quesma_builder.go b/quesma/v2/core/quesma_builder.go index fd1b3bc4b..004e7fcfb 100644 --- a/quesma/v2/core/quesma_builder.go +++ b/quesma/v2/core/quesma_builder.go @@ -9,7 +9,7 @@ import ( type Quesma struct { pipelines []PipelineBuilder - dependencies *Dependencies + dependencies Dependencies } func NewQuesma() *Quesma { @@ -18,7 +18,18 @@ func NewQuesma() *Quesma { } } -func (quesma *Quesma) SetDependencies(dependencies *Dependencies) { +func (quesma *Quesma) GetChildComponents() []any { + + componentList := make([]any, 0) + + for _, pipeline := range quesma.pipelines { + componentList = append(componentList, pipeline) + } + + return componentList +} + +func (quesma *Quesma) SetDependencies(dependencies Dependencies) { quesma.dependencies = dependencies } @@ -111,35 +122,32 @@ func (quesma *Quesma) buildInternal() (QuesmaBuilder, error) { return quesma, nil } -func (quesma *Quesma) injectDependencies() error { +func (quesma *Quesma) injectDependencies(tree *ComponentTreeNode) error { if quesma.dependencies == nil { return fmt.Errorf("dependencies not set") } - // - // We should have a better way to traverse the pipeline graph - // maybe we should have an `getSubComponents` method in every component - // - for _, pipeline := range quesma.pipelines { - quesma.dependencies.InjectDependenciesInto(pipeline) - for _, conn := range pipeline.GetFrontendConnectors() { - quesma.dependencies.InjectDependenciesInto(conn) + tree.walk(func(n *ComponentTreeNode) { + quesma.dependencies.InjectDependenciesInto(n.Component) + }) - if httpConn, ok := conn.(HTTPFrontendConnector); ok { - router := httpConn.GetRouter() - quesma.dependencies.InjectDependenciesInto(router) - } - } - for _, proc := range pipeline.GetProcessors() { - quesma.dependencies.InjectDependenciesInto(proc) - } - for _, conn := range pipeline.GetBackendConnectors() { - quesma.dependencies.InjectDependenciesInto(conn) - } - } return nil } +func (quesma *Quesma) printTree(tree *ComponentTreeNode) { + + fmt.Println("Component tree:\n---") + tree.walk(func(n *ComponentTreeNode) { + + for i := 0; i < n.Level; i++ { + fmt.Print(" ") + } + + fmt.Println(n.Id) + }) + fmt.Println("---") +} + func (quesma *Quesma) Build() (QuesmaBuilder, error) { _, err := quesma.buildInternal() @@ -147,11 +155,18 @@ func (quesma *Quesma) Build() (QuesmaBuilder, error) { return nil, fmt.Errorf("failed to build quesma instance: %v", err) } - err = quesma.injectDependencies() + treeBuilder := NewComponentToInitializeProviderBuilder() + tree := treeBuilder.BuildComponentTree(quesma) + + err = quesma.injectDependencies(tree) if err != nil { return nil, fmt.Errorf("failed to inject dependencies: %v", err) } + if traceDependencyInjection { + quesma.printTree(tree) + } + return quesma, nil } diff --git a/quesma/v2/core/quesma_pipeline.go b/quesma/v2/core/quesma_pipeline.go index 6f89670aa..396fe9c46 100644 --- a/quesma/v2/core/quesma_pipeline.go +++ b/quesma/v2/core/quesma_pipeline.go @@ -18,6 +18,22 @@ func NewPipeline() *Pipeline { } } +func (p *Pipeline) GetChildComponents() []any { + var components []any + + for _, conn := range p.FrontendConnectors { + components = append(components, conn) + } + for _, proc := range p.Processors { + components = append(components, proc) + } + for _, conn := range p.BackendConnectors { + components = append(components, conn) + } + + return components +} + func (p *Pipeline) AddFrontendConnector(conn FrontendConnector) { p.FrontendConnectors = append(p.FrontendConnectors, conn) }