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
 }

Reply via email to