This is an automated email from the ASF dual-hosted git repository.
robocanic pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git
The following commit(s) were added to refs/heads/develop by this push:
new 8eea7ed5 feat: support components to start in dependency order (#1370)
8eea7ed5 is described below
commit 8eea7ed59365f4b5d28c9f8e39f9a7574a414876
Author: EVERFID <[email protected]>
AuthorDate: Tue Dec 23 19:06:18 2025 +0800
feat: support components to start in dependency order (#1370)
* feat: support components to start in dependency order
* imporve
* fix
* fix error import
---
pkg/console/component.go | 7 +
pkg/console/counter/component.go | 7 +
pkg/core/bootstrap/bootstrap.go | 178 ++++++++++++--------
pkg/core/discovery/component.go | 7 +
pkg/core/engine/component.go | 8 +
pkg/core/events/component.go | 4 +
pkg/core/manager/component.go | 6 +
pkg/core/runtime/component.go | 5 +
pkg/core/runtime/dependency_graph.go | 206 +++++++++++++++++++++++
pkg/core/runtime/dependency_graph_test.go | 271 ++++++++++++++++++++++++++++++
pkg/core/store/component.go | 6 +
pkg/diagnostics/server.go | 4 +
12 files changed, 635 insertions(+), 74 deletions(-)
diff --git a/pkg/console/component.go b/pkg/console/component.go
index 2457a9de..8a9dd777 100644
--- a/pkg/console/component.go
+++ b/pkg/console/component.go
@@ -51,6 +51,13 @@ type consoleWebServer struct {
cs consolectx.Context
}
+func (c *consoleWebServer) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.ResourceManager, // Console needs Manager for resource
operations
+ // Note: No need to list ResourceStore explicitly as Manager
already depends on it
+ }
+}
+
func (c *consoleWebServer) Type() runtime.ComponentType {
return runtime.Console
}
diff --git a/pkg/console/counter/component.go b/pkg/console/counter/component.go
index c38d7fe8..0217ee2f 100644
--- a/pkg/console/counter/component.go
+++ b/pkg/console/counter/component.go
@@ -38,6 +38,13 @@ type ManagerComponent interface {
var _ ManagerComponent = &managerComponent{}
+func (c *managerComponent) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.ResourceStore,
+ runtime.EventBus, // Counter depends on EventBus to subscribe
to events
+ }
+}
+
type managerComponent struct {
manager CounterManager
}
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index 7abcae7d..f3ef6f0d 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -19,9 +19,9 @@ package bootstrap
import (
"context"
+ "fmt"
- "github.com/pkg/errors"
-
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
"github.com/apache/dubbo-admin/pkg/config/app"
"github.com/apache/dubbo-admin/pkg/console/counter"
"github.com/apache/dubbo-admin/pkg/core/logger"
@@ -34,38 +34,16 @@ func Bootstrap(appCtx context.Context, cfg app.AdminConfig)
(runtime.Runtime, er
if err != nil {
return nil, err
}
- // 0. initialize event bus
- if err := initEventBus(builder); err != nil {
- return nil, err
- }
- // 1. initialize resource store
- if err := initResourceStore(cfg, builder); err != nil {
- return nil, err
- }
- // 2. initialize discovery
- if err := initializeResourceDiscovery(builder); err != nil {
- return nil, err
- }
- // 3. initialize engine
- if err := initializeResourceEngine(builder); err != nil {
- return nil, err
- }
- // 4. initialize resource manager
- if err := initResourceManager(builder); err != nil {
- return nil, err
- }
- // 5. initialize console
- if err := initializeConsole(builder); err != nil {
- return nil, err
- }
- // 6. initialize counter manager
- if err := initializeCounterManager(builder); err != nil {
+
+ // Use smart bootstrapper for intelligent component initialization
+ bootstrapper := NewSmartBootstrapper(builder)
+
+ // Initialize all components in dependency order
+ if err := bootstrapper.bootstrapComponents(appCtx, cfg); err != nil {
return nil, err
}
- // 7. initialize diagnostics
- if err := initializeDiagnoticsServer(builder); err != nil {
- logger.Errorf("got error when init diagnotics server %s", err)
- }
+
+ // Build and return runtime
rt, err := builder.Build()
if err != nil {
return nil, err
@@ -73,67 +51,119 @@ func Bootstrap(appCtx context.Context, cfg
app.AdminConfig) (runtime.Runtime, er
return rt, nil
}
-func initEventBus(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().EventBus()
- if err != nil {
- return err
- }
- return initAndActivateComponent(builder, comp)
+// SmartBootstrapper handles intelligent component initialization
+type SmartBootstrapper struct {
+ builder *runtime.Builder
}
-func initResourceStore(cfg app.AdminConfig, builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().ResourceStore()
- if err != nil {
- return errors.Wrapf(err, "could not retrieve resource store %s
component", cfg.Store.Type)
+// NewSmartBootstrapper creates a new smart bootstrapper
+func NewSmartBootstrapper(builder *runtime.Builder) *SmartBootstrapper {
+ return &SmartBootstrapper{
+ builder: builder,
}
- return initAndActivateComponent(builder, comp)
}
-func initResourceManager(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().ResourceManager()
+
+// bootstrapComponents initializes all components in dependency order
+func (sb *SmartBootstrapper) bootstrapComponents(
+ ctx context.Context,
+ cfg app.AdminConfig,
+) error {
+ logger.Info("Starting smart component bootstrap...")
+
+ // Gather all components to initialize
+ components, err := sb.gatherComponents()
if err != nil {
- return err
+ return bizerror.Wrap(err, bizerror.UnknownError, "failed to
gather components")
}
- return initAndActivateComponent(builder, comp)
-}
-func initializeConsole(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().Console()
+ // Sort components by dependencies
+ ordered, err := sb.sortComponents(components)
if err != nil {
- return err
+ return bizerror.Wrap(err, bizerror.UnknownError, "failed to
sort components by dependencies")
}
- return initAndActivateComponent(builder, comp)
-}
-func initializeResourceDiscovery(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().ResourceDiscovery()
- if err != nil {
- return err
+ // Initialize components in order
+ for i, comp := range ordered {
+ logger.Infof("[%d/%d] Initializing %s...", i+1, len(ordered),
comp.Type())
+ if err := initAndActivateComponent(sb.builder, comp); err !=
nil {
+ return bizerror.Wrap(err, bizerror.UnknownError,
fmt.Sprintf("failed to initialize component %s", comp.Type()))
+ }
+ logger.Infof("[%d/%d] %s initialized successfully", i+1,
len(ordered), comp.Type())
}
- return initAndActivateComponent(builder, comp)
+
+ logger.Info("All components bootstrapped successfully")
+ return nil
}
-func initializeResourceEngine(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().ResourceEngine()
- if err != nil {
- return err
+// gatherComponents collects all components that need to be initialized
+func (sb *SmartBootstrapper) gatherComponents() ([]runtime.Component, error) {
+ components := []runtime.Component{}
+
+ // Core components
+ coreComps := []struct {
+ name string
+ getter func() (runtime.Component, error)
+ }{
+ {"EventBus", runtime.ComponentRegistry().EventBus},
+ {"ResourceStore", runtime.ComponentRegistry().ResourceStore},
+ {"ResourceDiscovery",
runtime.ComponentRegistry().ResourceDiscovery},
+ {"ResourceEngine", runtime.ComponentRegistry().ResourceEngine},
+ {"ResourceManager",
runtime.ComponentRegistry().ResourceManager},
+ {"Console", runtime.ComponentRegistry().Console},
}
- return initAndActivateComponent(builder, comp)
-}
-func initializeDiagnoticsServer(builder *runtime.Builder) error {
- comp, err :=
runtime.ComponentRegistry().Get(diagnostics.DiagnosticsServer)
- if err != nil {
- return err
+ for _, comp := range coreComps {
+ c, err := comp.getter()
+ if err != nil {
+ return nil, bizerror.Wrap(err, bizerror.UnknownError,
fmt.Sprintf("failed to get component %s", comp.name))
+ }
+ components = append(components, c)
+ }
+
+ // Optional components
+ optionalComps := []struct {
+ name string
+ typ runtime.ComponentType
+ }{
+ {"CounterManager", counter.ComponentType},
+ {"DiagnosticsServer", diagnostics.DiagnosticsServer},
}
- return initAndActivateComponent(builder, comp)
+
+ for _, comp := range optionalComps {
+ c, err := runtime.ComponentRegistry().Get(comp.typ)
+ if err != nil {
+ logger.Warnf("Optional component %s not available: %v",
comp.name, err)
+ continue
+ }
+ components = append(components, c)
+ }
+
+ return components, nil
}
-func initializeCounterManager(builder *runtime.Builder) error {
- comp, err := runtime.ComponentRegistry().Get(counter.ComponentType)
+// sortComponents sorts components by dependency order
+func (sb *SmartBootstrapper) sortComponents(
+ components []runtime.Component,
+) ([]runtime.Component, error) {
+ // Build dependency graph and perform topological sort
+ graph := runtime.NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
if err != nil {
- return err
+ return nil, err
}
- return initAndActivateComponent(builder, comp)
+
+ // Log initialization order
+ logger.Info("Component initialization order:")
+ for i, comp := range sorted {
+ deps := comp.RequiredDependencies()
+ if len(deps) > 0 {
+ logger.Infof(" %d. %s (depends on: %v)", i+1,
comp.Type(), deps)
+ } else {
+ logger.Infof(" %d. %s (no dependencies)", i+1,
comp.Type())
+ }
+ }
+
+ return sorted, nil
}
func initAndActivateComponent(builder *runtime.Builder, comp
runtime.Component) error {
@@ -143,7 +173,7 @@ func initAndActivateComponent(builder *runtime.Builder,
comp runtime.Component)
}
logger.Infof("%s initialized successfully", comp.Type())
if err := builder.ActivateComponent(comp); err != nil {
- return errors.Wrapf(err, "failed to activate %s", comp.Type())
+ return bizerror.Wrap(err, bizerror.UnknownError,
fmt.Sprintf("failed to activate %s", comp.Type()))
}
return nil
}
diff --git a/pkg/core/discovery/component.go b/pkg/core/discovery/component.go
index 0863cf5e..68358e72 100644
--- a/pkg/core/discovery/component.go
+++ b/pkg/core/discovery/component.go
@@ -57,6 +57,13 @@ type discoveryComponent struct {
subscriptionMgr events.SubscriptionManager
}
+func (d *discoveryComponent) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.EventBus, // Discovery needs EventBus for event
emission
+ runtime.ResourceStore, // Discovery needs Store for resource
storage
+ }
+}
+
func newDiscoveryComponent() Component {
return &discoveryComponent{
informers: make(map[string]Informers),
diff --git a/pkg/core/engine/component.go b/pkg/core/engine/component.go
index 6d366da3..818fa8d1 100644
--- a/pkg/core/engine/component.go
+++ b/pkg/core/engine/component.go
@@ -60,6 +60,14 @@ func newEngineComponent() Component {
subscribers: make([]events.Subscriber, 0),
}
}
+
+func (e *engineComponent) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.EventBus,
+ runtime.ResourceStore,
+ }
+}
+
func (e *engineComponent) Type() runtime.ComponentType {
return runtime.ResourceEngine
}
diff --git a/pkg/core/events/component.go b/pkg/core/events/component.go
index 97d07512..dc2b6db7 100644
--- a/pkg/core/events/component.go
+++ b/pkg/core/events/component.go
@@ -43,6 +43,10 @@ type eventBus struct {
subscriberDir map[model.ResourceKind]Subscribers
}
+func (b *eventBus) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{} // EventBus has no dependencies
+}
+
func (b *eventBus) Type() runtime.ComponentType {
return runtime.EventBus
}
diff --git a/pkg/core/manager/component.go b/pkg/core/manager/component.go
index e12f3c31..ae7d7006 100644
--- a/pkg/core/manager/component.go
+++ b/pkg/core/manager/component.go
@@ -41,6 +41,12 @@ type resourceManagerComponent struct {
rm ResourceManager
}
+func (r *resourceManagerComponent) RequiredDependencies()
[]runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.ResourceStore, // Manager needs Store to be initialized
first
+ }
+}
+
func (r *resourceManagerComponent) Type() runtime.ComponentType {
return runtime.ResourceManager
}
diff --git a/pkg/core/runtime/component.go b/pkg/core/runtime/component.go
index 88dcd3fe..a0362c0e 100644
--- a/pkg/core/runtime/component.go
+++ b/pkg/core/runtime/component.go
@@ -42,7 +42,12 @@ type Attribute interface {
// Type returns the type of the component
Type() ComponentType
// Order indicates the order of the component during bootstrap, the
bigger will be started first
+ // Deprecated: Use RequiredDependencies() instead for explicit
dependency management
Order() int
+ // RequiredDependencies returns the component types that must be
initialized before this component
+ // The system will ensure all required dependencies are initialized
first, or fail with a clear error
+ // Return an empty slice if the component has no dependencies
+ RequiredDependencies() []ComponentType
}
// Component defines a process that will be run in the application
diff --git a/pkg/core/runtime/dependency_graph.go
b/pkg/core/runtime/dependency_graph.go
new file mode 100644
index 00000000..bf42d390
--- /dev/null
+++ b/pkg/core/runtime/dependency_graph.go
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package runtime
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/apache/dubbo-admin/pkg/common/bizerror"
+)
+
+// DependencyGraph represents the dependency relationships between components
+// The adjacency list is stored in reverse: if A depends on B, we store B -> A
+// This makes topological sort return components in initialization order
+type DependencyGraph struct {
+ components map[ComponentType]Component
+ adjList map[ComponentType][]ComponentType // reverse adjacency list:
dependent <- dependee
+}
+
+// NewDependencyGraph creates a new dependency graph from components
+func NewDependencyGraph(components []Component) *DependencyGraph {
+ dg := &DependencyGraph{
+ components: make(map[ComponentType]Component),
+ adjList: make(map[ComponentType][]ComponentType),
+ }
+
+ // Initialize adjacency list for all components
+ for _, comp := range components {
+ dg.components[comp.Type()] = comp
+ dg.adjList[comp.Type()] = []ComponentType{}
+ }
+
+ // Build reverse adjacency list
+ // If component A depends on B, we create edge B -> A
+ // This way, the adjacency list represents "who depends on me"
+ for _, comp := range components {
+ for _, dependency := range comp.RequiredDependencies() {
+ // dependency -> comp.Type()
+ // Meaning: dependency is required by comp.Type()
+ dg.adjList[dependency] = append(dg.adjList[dependency],
comp.Type())
+ }
+ }
+
+ return dg
+}
+
+// TopologicalSort performs topological sort using Kahn's algorithm
+// Returns components in initialization order (dependencies first)
+func (dg *DependencyGraph) TopologicalSort() ([]Component, error) {
+ // 1. Validate all dependencies exist
+ if err := dg.validate(); err != nil {
+ return nil, err
+ }
+
+ // 2. Calculate in-degree for each node
+ // In-degree = number of dependencies
+ indegree := make(map[ComponentType]int)
+ for _, comp := range dg.components {
+ indegree[comp.Type()] = len(comp.RequiredDependencies())
+ }
+
+ // 3. Find all nodes with in-degree 0 (no dependencies)
+ queue := []ComponentType{}
+ for typ, deg := range indegree {
+ if deg == 0 {
+ queue = append(queue, typ)
+ }
+ }
+
+ // 4. Process nodes in topological order
+ result := []Component{}
+ visited := make(map[ComponentType]bool)
+
+ for len(queue) > 0 {
+ // Sort queue alphabetically for deterministic ordering
+ sort.Strings(queue)
+
+ // Pop the first element
+ current := queue[0]
+ queue = queue[1:]
+
+ result = append(result, dg.components[current])
+ visited[current] = true
+
+ // Process all components that depend on current
+ for _, dependent := range dg.adjList[current] {
+ indegree[dependent]--
+ if indegree[dependent] == 0 && !visited[dependent] {
+ queue = append(queue, dependent)
+ }
+ }
+ }
+
+ // 5. Check for circular dependencies
+ if len(result) != len(dg.components) {
+ cycle := dg.findCycle()
+ return nil, newCircularDependencyError(cycle)
+ }
+
+ return result, nil
+}
+
+// validate checks that all declared dependencies exist
+func (dg *DependencyGraph) validate() error {
+ for _, comp := range dg.components {
+ for _, dependency := range comp.RequiredDependencies() {
+ if _, exists := dg.components[dependency]; !exists {
+ return bizerror.Wrap(
+ fmt.Errorf("missing dependency %q",
dependency),
+ bizerror.ConfigError,
+ fmt.Sprintf(
+ "component %q requires missing
dependency %q. "+
+ "Hint: Make sure %q is
registered in ComponentRegistry()",
+ comp.Type(), dependency,
dependency,
+ ),
+ )
+ }
+ }
+ }
+ return nil
+}
+
+// findCycle uses DFS to find a circular dependency
+func (dg *DependencyGraph) findCycle() []ComponentType {
+ visited := make(map[ComponentType]bool)
+ recStack := make(map[ComponentType]bool)
+ var cycle []ComponentType
+
+ var dfs func(ComponentType) bool
+ dfs = func(node ComponentType) bool {
+ visited[node] = true
+ recStack[node] = true
+ cycle = append(cycle, node)
+
+ // Visit dependencies (not dependents)
+ for _, dependency := range
dg.components[node].RequiredDependencies() {
+ if !visited[dependency] {
+ if dfs(dependency) {
+ return true
+ }
+ } else if recStack[dependency] {
+ // Found cycle, extract the cycle path
+ for i, typ := range cycle {
+ if typ == dependency {
+ cycle = cycle[i:]
+ return true
+ }
+ }
+ }
+ }
+
+ recStack[node] = false
+ cycle = cycle[:len(cycle)-1]
+ return false
+ }
+
+ for typ := range dg.components {
+ if !visited[typ] {
+ if dfs(typ) {
+ return cycle
+ }
+ }
+ }
+
+ return nil
+}
+
+// newCircularDependencyError creates a circular dependency error
+func newCircularDependencyError(cycle []ComponentType) error {
+ if len(cycle) == 0 {
+ return bizerror.New(bizerror.ConfigError, "circular dependency
detected")
+ }
+
+ // Format cycle path: A -> B -> C -> A
+ path := make([]string, len(cycle)+1)
+ for i, typ := range cycle {
+ path[i] = string(typ)
+ }
+ path[len(cycle)] = string(cycle[0])
+ cyclePath := strings.Join(path, " -> ")
+
+ return bizerror.New(
+ bizerror.ConfigError,
+ fmt.Sprintf(
+ "circular dependency detected: %s. "+
+ "Please check the RequiredDependencies()
methods of these components and break the cycle",
+ cyclePath,
+ ),
+ )
+}
diff --git a/pkg/core/runtime/dependency_graph_test.go
b/pkg/core/runtime/dependency_graph_test.go
new file mode 100644
index 00000000..ef8bbca4
--- /dev/null
+++ b/pkg/core/runtime/dependency_graph_test.go
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package runtime
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Mock component for testing
+type mockComponent struct {
+ typ ComponentType
+ order int
+ deps []ComponentType
+}
+
+func (m *mockComponent) Type() ComponentType {
+ return m.typ
+}
+func (m *mockComponent) Order() int {
+ return m.order
+}
+func (m *mockComponent) RequiredDependencies() []ComponentType {
+ return m.deps
+}
+func (m *mockComponent) Init(ctx BuilderContext) error {
+ return nil
+}
+func (m *mockComponent) Start(Runtime, <-chan struct{}) error {
+ return nil
+}
+
+func TestDependencyGraph_SimpleLinearDependency(t *testing.T) {
+ // A -> B -> C
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps:
[]ComponentType{"B"}},
+ &mockComponent{typ: "B", order: 200, deps:
[]ComponentType{"C"}},
+ &mockComponent{typ: "C", order: 300, deps: []ComponentType{}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 3, len(sorted))
+ // C should be first, then B, then A
+ assert.Equal(t, ComponentType("C"), sorted[0].Type())
+ assert.Equal(t, ComponentType("B"), sorted[1].Type())
+ assert.Equal(t, ComponentType("A"), sorted[2].Type())
+}
+
+func TestDependencyGraph_DiamondDependency(t *testing.T) {
+ // A depends on B and C, both B and C depend on D
+ // A
+ // / \
+ // B C
+ // \ /
+ // D
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps: []ComponentType{"B",
"C"}},
+ &mockComponent{typ: "B", order: 200, deps:
[]ComponentType{"D"}},
+ &mockComponent{typ: "C", order: 300, deps:
[]ComponentType{"D"}},
+ &mockComponent{typ: "D", order: 400, deps: []ComponentType{}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 4, len(sorted))
+ // D must be first
+ assert.Equal(t, ComponentType("D"), sorted[0].Type())
+ // B and C can be in any order (alphabetically: B before C)
+ assert.Equal(t, ComponentType("B"), sorted[1].Type())
+ assert.Equal(t, ComponentType("C"), sorted[2].Type())
+ // A must be last
+ assert.Equal(t, ComponentType("A"), sorted[3].Type())
+}
+
+func TestDependencyGraph_CircularDependency_TwoNodes(t *testing.T) {
+ // A -> B -> A (circular)
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps:
[]ComponentType{"B"}},
+ &mockComponent{typ: "B", order: 200, deps:
[]ComponentType{"A"}},
+ }
+
+ graph := NewDependencyGraph(components)
+ _, err := graph.TopologicalSort()
+
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "circular dependency detected")
+ assert.Contains(t, err.Error(), "A")
+ assert.Contains(t, err.Error(), "B")
+}
+
+func TestDependencyGraph_CircularDependency_ThreeNodes(t *testing.T) {
+ // A -> B -> C -> A (circular)
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps:
[]ComponentType{"B"}},
+ &mockComponent{typ: "B", order: 200, deps:
[]ComponentType{"C"}},
+ &mockComponent{typ: "C", order: 300, deps:
[]ComponentType{"A"}},
+ }
+
+ graph := NewDependencyGraph(components)
+ _, err := graph.TopologicalSort()
+
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "circular dependency detected")
+ errMsg := err.Error()
+ assert.Contains(t, errMsg, "A")
+ assert.Contains(t, errMsg, "B")
+ assert.Contains(t, errMsg, "C")
+}
+
+func TestDependencyGraph_MissingDependency(t *testing.T) {
+ // A depends on non-existent component "X"
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps:
[]ComponentType{"X"}},
+ }
+
+ graph := NewDependencyGraph(components)
+ _, err := graph.TopologicalSort()
+
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "missing dependency")
+ assert.Contains(t, err.Error(), "X")
+}
+
+func TestDependencyGraph_NoDependencies(t *testing.T) {
+ // All components independent, should sort alphabetically
(deterministic)
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps: []ComponentType{}},
+ &mockComponent{typ: "B", order: 300, deps: []ComponentType{}},
+ &mockComponent{typ: "C", order: 200, deps: []ComponentType{}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 3, len(sorted))
+ // With no dependencies, components are sorted alphabetically
+ assert.Equal(t, ComponentType("A"), sorted[0].Type())
+ assert.Equal(t, ComponentType("B"), sorted[1].Type())
+ assert.Equal(t, ComponentType("C"), sorted[2].Type())
+}
+
+func TestDependencyGraph_ComplexDependencies(t *testing.T) {
+ // Simulating real dubbo-admin components
+ // EventBus (no deps)
+ // Store -> EventBus
+ // Discovery -> EventBus, Store
+ // Engine -> EventBus, Store
+ // Manager -> Store
+ // Console -> Manager
+ components := []Component{
+ &mockComponent{typ: "EventBus", order: 1000, deps:
[]ComponentType{}},
+ &mockComponent{typ: "Store", order: 900, deps:
[]ComponentType{"EventBus"}},
+ &mockComponent{typ: "Discovery", order: 800, deps:
[]ComponentType{"EventBus", "Store"}},
+ &mockComponent{typ: "Engine", order: 700, deps:
[]ComponentType{"EventBus", "Store"}},
+ &mockComponent{typ: "Manager", order: 600, deps:
[]ComponentType{"Store"}},
+ &mockComponent{typ: "Console", order: 500, deps:
[]ComponentType{"Manager"}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 6, len(sorted))
+
+ // Create a map for easier verification
+ orderMap := make(map[ComponentType]int)
+ for i, comp := range sorted {
+ orderMap[comp.Type()] = i
+ }
+
+ // Verify dependency constraints
+ assert.Less(t, orderMap["EventBus"], orderMap["Store"], "EventBus must
initialize before Store")
+ assert.Less(t, orderMap["EventBus"], orderMap["Discovery"], "EventBus
must initialize before Discovery")
+ assert.Less(t, orderMap["Store"], orderMap["Discovery"], "Store must
initialize before Discovery")
+ assert.Less(t, orderMap["EventBus"], orderMap["Engine"], "EventBus must
initialize before Engine")
+ assert.Less(t, orderMap["Store"], orderMap["Engine"], "Store must
initialize before Engine")
+ assert.Less(t, orderMap["Store"], orderMap["Manager"], "Store must
initialize before Manager")
+ assert.Less(t, orderMap["Manager"], orderMap["Console"], "Manager must
initialize before Console")
+}
+
+func TestDependencyGraph_SelfDependency(t *testing.T) {
+ // Component depends on itself (edge case)
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps:
[]ComponentType{"A"}},
+ }
+
+ graph := NewDependencyGraph(components)
+ _, err := graph.TopologicalSort()
+
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "circular dependency detected")
+ assert.Contains(t, err.Error(), "A -> A")
+}
+
+func TestDependencyGraph_MultipleDependenciesSameLevel(t *testing.T) {
+ // A depends on B, C, D (all at same level)
+ // B, C, D have no dependencies
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps: []ComponentType{"B",
"C", "D"}},
+ &mockComponent{typ: "B", order: 200, deps: []ComponentType{}},
+ &mockComponent{typ: "C", order: 300, deps: []ComponentType{}},
+ &mockComponent{typ: "D", order: 400, deps: []ComponentType{}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 4, len(sorted))
+
+ // Create a map for easier verification
+ orderMap := make(map[ComponentType]int)
+ for i, comp := range sorted {
+ orderMap[comp.Type()] = i
+ }
+
+ // All dependencies must come before A
+ assert.Less(t, orderMap["B"], orderMap["A"])
+ assert.Less(t, orderMap["C"], orderMap["A"])
+ assert.Less(t, orderMap["D"], orderMap["A"])
+
+ // A must be last
+ assert.Equal(t, ComponentType("A"), sorted[3].Type())
+}
+
+func TestDependencyGraph_EmptyComponents(t *testing.T) {
+ // Empty component list
+ components := []Component{}
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 0, len(sorted))
+}
+
+func TestDependencyGraph_SingleComponent(t *testing.T) {
+ // Single component with no dependencies
+ components := []Component{
+ &mockComponent{typ: "A", order: 100, deps: []ComponentType{}},
+ }
+
+ graph := NewDependencyGraph(components)
+ sorted, err := graph.TopologicalSort()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(sorted))
+ assert.Equal(t, ComponentType("A"), sorted[0].Type())
+}
diff --git a/pkg/core/store/component.go b/pkg/core/store/component.go
index 3fbc0b3f..6eadc7f2 100644
--- a/pkg/core/store/component.go
+++ b/pkg/core/store/component.go
@@ -48,6 +48,12 @@ type storeComponent struct {
stores map[coremodel.ResourceKind]ManagedResourceStore
}
+func (sc *storeComponent) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{
+ runtime.EventBus, // Store may need EventBus for event emission
+ }
+}
+
func newStoreComponent() *storeComponent {
return &storeComponent{
stores: make(map[coremodel.ResourceKind]ManagedResourceStore),
diff --git a/pkg/diagnostics/server.go b/pkg/diagnostics/server.go
index 4d707b36..7749b915 100644
--- a/pkg/diagnostics/server.go
+++ b/pkg/diagnostics/server.go
@@ -45,6 +45,10 @@ var (
_ runtime.Component = &diagnosticsServer{}
)
+func (s *diagnosticsServer) RequiredDependencies() []runtime.ComponentType {
+ return []runtime.ComponentType{} // Diagnostics server has no
dependencies
+}
+
func (s *diagnosticsServer) Type() runtime.ComponentType {
return DiagnosticsServer
}