robocanic commented on code in PR #1414:
URL: https://github.com/apache/dubbo-admin/pull/1414#discussion_r2797446764
##########
pkg/console/service/service.go:
##########
@@ -522,3 +522,174 @@ func isArgumentRoute(condition string) bool {
}
return false
}
+
+// SearchServiceAsCrossLinkedList builds a service dependency graph.
+// If serviceName is provided, it finds applications that consume or provide
that specific service.
+// If serviceName is empty, it returns all service dependencies in the mesh.
+// It constructs nodes (applications) and edges (dependencies) for graph
visualization.
+func SearchServiceAsCrossLinkedList(ctx consolectx.Context, req
*model.ServiceGraphReq) (*model.GraphData, error) {
+ // Build indexes conditionally based on whether serviceName is provided
+ consumerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+ providerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+
+ // Only filter by serviceName if it's provided
+ if req.ServiceName != "" {
+ consumerIndexes[index.ByServiceConsumerServiceName] =
req.ServiceName
+ providerIndexes[index.ByServiceProviderServiceName] =
req.ServiceName
+ }
+
+ // Use ListByIndexes instead of PageListByIndexes to get all related
resources
+ // since we need complete dependency graph, not paginated results
+ consumers, err :=
manager.ListByIndexes[*meshresource.ServiceConsumerMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceConsumerMetadataKind,
+ consumerIndexes)
+ if err != nil {
+ logger.Errorf("get service consumer for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
consumer failed, please try again")
+ }
+
+ providers, err :=
manager.ListByIndexes[*meshresource.ServiceProviderMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceProviderMetadataKind,
+ providerIndexes)
+ if err != nil {
+ logger.Errorf("get service provider for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
provider failed, please try again")
+ }
+
+ // Collect all unique applications (both consumers and providers)
+ consumerApps := make(map[string]bool)
Review Comment:
Suggestion:
1. golang中没有内置的set,但可以用map[string]strunct{}来替代,value是一个空的struct,不占存储空间
2. 可以使用工具类lancet中的set来替代,具体用法可以参考其他代码
##########
pkg/console/service/service.go:
##########
@@ -522,3 +522,174 @@ func isArgumentRoute(condition string) bool {
}
return false
}
+
+// SearchServiceAsCrossLinkedList builds a service dependency graph.
+// If serviceName is provided, it finds applications that consume or provide
that specific service.
+// If serviceName is empty, it returns all service dependencies in the mesh.
+// It constructs nodes (applications) and edges (dependencies) for graph
visualization.
+func SearchServiceAsCrossLinkedList(ctx consolectx.Context, req
*model.ServiceGraphReq) (*model.GraphData, error) {
+ // Build indexes conditionally based on whether serviceName is provided
+ consumerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+ providerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+
+ // Only filter by serviceName if it's provided
+ if req.ServiceName != "" {
+ consumerIndexes[index.ByServiceConsumerServiceName] =
req.ServiceName
+ providerIndexes[index.ByServiceProviderServiceName] =
req.ServiceName
+ }
+
+ // Use ListByIndexes instead of PageListByIndexes to get all related
resources
+ // since we need complete dependency graph, not paginated results
+ consumers, err :=
manager.ListByIndexes[*meshresource.ServiceConsumerMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceConsumerMetadataKind,
+ consumerIndexes)
+ if err != nil {
+ logger.Errorf("get service consumer for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
consumer failed, please try again")
+ }
+
+ providers, err :=
manager.ListByIndexes[*meshresource.ServiceProviderMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceProviderMetadataKind,
+ providerIndexes)
+ if err != nil {
+ logger.Errorf("get service provider for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
provider failed, please try again")
+ }
+
+ // Collect all unique applications (both consumers and providers)
+ consumerApps := make(map[string]bool)
+ for _, consumer := range consumers {
+ if consumer.Spec != nil {
+ consumerApps[consumer.Spec.ConsumerAppName] = true
+ }
+ }
+
+ providerApps := make(map[string]bool)
+ for _, provider := range providers {
+ if provider.Spec != nil {
+ providerApps[provider.Spec.ProviderAppName] = true
+ }
+ }
+
+ allApps := make(map[string]bool)
+ for app := range consumerApps {
+ allApps[app] = true
+ }
+ for app := range providerApps {
+ allApps[app] = true
+ }
+
+ nodes := make([]model.GraphNode, 0, len(allApps))
+ edges := make([]model.GraphEdge, 0)
+
+ // Build app to service instances mapping
+ // For providers: map providerAppName -> list of instances providing
this service
+ // For consumers: map consumerAppName -> empty list (consumers don't
provide instances)
+ appInstances := make(map[string][]*meshresource.InstanceResource)
+
+ // Get all instances for the mesh first
+ allInstances, err :=
manager.ListByIndexes[*meshresource.InstanceResource](
+ ctx.ResourceManager(),
+ meshresource.InstanceKind,
+ map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ })
+ if err != nil {
+ logger.Errorf("get instances for mesh %s failed, cause: %v",
req.Mesh, err)
+ }
+
+ // Build app -> instances mapping
+ for _, instance := range allInstances {
+ if instance.Spec != nil && instance.Spec.AppName != "" {
+ appInstances[instance.Spec.AppName] =
append(appInstances[instance.Spec.AppName], instance)
+ }
+ }
+
+ // Build nodes for each app
+ // Provider nodes: data contains instances providing this service
+ // Consumer nodes: data is nil (consumers don't provide instances)
+ for appName := range allApps {
+ var instanceData interface{}
+
+ instances := make([]interface{}, 0)
+ if appInsts, ok := appInstances[appName]; ok {
+ for _, instance := range appInsts {
+ instances = append(instances,
toInstanceData(instance))
+ }
+ }
+ instanceData = instances
+
+ nodes = append(nodes, model.GraphNode{
+ ID: appName,
+ Label: appName,
+ Data: instanceData,
+ })
+ }
+ edgeKeyMap := make(map[string]struct{})
+
+ // Build edges between consumers and providers
+ // Only create edges between apps that actually have the service
relationship
+ for _, consumer := range consumers {
Review Comment:
Idea: 这里的每一个消费者应用都会有一条边指向提供者应用?这个关系太复杂。可不可以转换成这种结构:
<img width="738" height="419" alt="Image"
src="https://github.com/user-attachments/assets/7bf673f0-29b6-414c-a2ed-adaf912f6384"
/>
用服务作为一个中间节点,左边是提供者应用,右边是消费者应用
##########
pkg/console/service/service.go:
##########
@@ -522,3 +522,174 @@ func isArgumentRoute(condition string) bool {
}
return false
}
+
+// SearchServiceAsCrossLinkedList builds a service dependency graph.
+// If serviceName is provided, it finds applications that consume or provide
that specific service.
+// If serviceName is empty, it returns all service dependencies in the mesh.
+// It constructs nodes (applications) and edges (dependencies) for graph
visualization.
+func SearchServiceAsCrossLinkedList(ctx consolectx.Context, req
*model.ServiceGraphReq) (*model.GraphData, error) {
+ // Build indexes conditionally based on whether serviceName is provided
+ consumerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+ providerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+
+ // Only filter by serviceName if it's provided
+ if req.ServiceName != "" {
+ consumerIndexes[index.ByServiceConsumerServiceName] =
req.ServiceName
+ providerIndexes[index.ByServiceProviderServiceName] =
req.ServiceName
+ }
+
+ // Use ListByIndexes instead of PageListByIndexes to get all related
resources
+ // since we need complete dependency graph, not paginated results
+ consumers, err :=
manager.ListByIndexes[*meshresource.ServiceConsumerMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceConsumerMetadataKind,
+ consumerIndexes)
+ if err != nil {
+ logger.Errorf("get service consumer for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
consumer failed, please try again")
+ }
+
+ providers, err :=
manager.ListByIndexes[*meshresource.ServiceProviderMetadataResource](
+ ctx.ResourceManager(),
+ meshresource.ServiceProviderMetadataKind,
+ providerIndexes)
+ if err != nil {
+ logger.Errorf("get service provider for mesh %s failed, cause:
%v", req.Mesh, err)
+ return nil, bizerror.New(bizerror.InternalError, "get service
provider failed, please try again")
+ }
+
+ // Collect all unique applications (both consumers and providers)
+ consumerApps := make(map[string]bool)
+ for _, consumer := range consumers {
+ if consumer.Spec != nil {
+ consumerApps[consumer.Spec.ConsumerAppName] = true
+ }
+ }
+
+ providerApps := make(map[string]bool)
+ for _, provider := range providers {
+ if provider.Spec != nil {
+ providerApps[provider.Spec.ProviderAppName] = true
+ }
+ }
+
+ allApps := make(map[string]bool)
+ for app := range consumerApps {
+ allApps[app] = true
+ }
+ for app := range providerApps {
+ allApps[app] = true
+ }
+
+ nodes := make([]model.GraphNode, 0, len(allApps))
+ edges := make([]model.GraphEdge, 0)
+
+ // Build app to service instances mapping
+ // For providers: map providerAppName -> list of instances providing
this service
+ // For consumers: map consumerAppName -> empty list (consumers don't
provide instances)
+ appInstances := make(map[string][]*meshresource.InstanceResource)
+
+ // Get all instances for the mesh first
+ allInstances, err :=
manager.ListByIndexes[*meshresource.InstanceResource](
Review Comment:
Idea:
这里应该是想要把所有的实例搜出来,然后挂到每个应用下面,但这种做法在生产环境下万万不行,因为这里面会把所有的实例全部搜出来,如果实例数很多,可能会出现爆内存/DB打挂的情况。所以这里可以从交互入手,改一下交互,
在返回graph时不返回每个节点具体的数据,只返回id和label。用户点击一个节点时再请求对应节点(应用)的数据。这样,就不用在拓扑图这里捞这么多数据。
##########
pkg/console/service/service.go:
##########
@@ -522,3 +522,174 @@ func isArgumentRoute(condition string) bool {
}
return false
}
+
+// SearchServiceAsCrossLinkedList builds a service dependency graph.
+// If serviceName is provided, it finds applications that consume or provide
that specific service.
+// If serviceName is empty, it returns all service dependencies in the mesh.
+// It constructs nodes (applications) and edges (dependencies) for graph
visualization.
+func SearchServiceAsCrossLinkedList(ctx consolectx.Context, req
*model.ServiceGraphReq) (*model.GraphData, error) {
+ // Build indexes conditionally based on whether serviceName is provided
+ consumerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+ providerIndexes := map[string]string{
+ index.ByMeshIndex: req.Mesh,
+ }
+
+ // Only filter by serviceName if it's provided
+ if req.ServiceName != "" {
Review Comment:
Correction:
1. 不应该有这个判断,服务名是必须的参数,没有服务名,应该在handler里面就返回错误。后续的拓扑也是针对某一个服务来展开的。
2.
对于一个服务来说,serviceName不能完全定位一个服务,必须要serviceName:version:group——即[ServiceKey](https://github.com/apache/dubbo-admin/blob/d4d7a8f58ca67d3731d59b130afbf0044ccac50e/pkg/console/model/service.go#L120)才能完全定位一个服务。这里请求入参可以直接使用[BaseServiceReq](https://github.com/apache/dubbo-admin/blob/d4d7a8f58ca67d3731d59b130afbf0044ccac50e/pkg/console/model/service.go#L102)
3.
这里对于ServiceConsumerMetadataKind和ServiceProviderMetadataKind的索引还缺少对ServiceKey的索引,需要先在索引里面加上,然后用ServiceKey的索引来搜索这两个Resource
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]