This is an automated email from the ASF dual-hosted git repository.

alexstocks 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 90fdfc2b feat: support memory type of store (#1332)
90fdfc2b is described below

commit 90fdfc2b25602379cf7c17314a1edc1a355da682
Author: robb <[email protected]>
AuthorDate: Sun Oct 12 11:28:15 2025 +0800

    feat: support memory type of store (#1332)
    
    * feat: support memory type of store
    
    * fix: muilti indexes should use intersecection
    
    * simplify code
    
    Co-authored-by: Copilot <[email protected]>
    
    * refractor: GetByKeys return a list instead of map
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 app/dubbo-admin/dubbo-admin.yaml                   |   3 +-
 pkg/common/{errors/error.go => bizerror/common.go} |   2 +-
 pkg/console/handler/configurator_rule.go           |   2 +-
 pkg/console/handler/tag_rule.go                    |   2 +-
 pkg/console/service/application.go                 |  10 +-
 pkg/console/service/instance.go                    |   2 +-
 pkg/console/service/service.go                     |   2 +-
 pkg/core/bootstrap/bootstrap.go                    |   6 -
 .../memory/memory.go => core/bootstrap/init.go}    |  14 +-
 pkg/core/manager/manager.go                        |   8 +-
 pkg/core/manager/manager_helper.go                 |  14 +-
 pkg/core/store/factory.go                          |  10 +-
 pkg/core/store/index/common.go                     |   4 +-
 pkg/core/store/index/instance.go                   |   6 +-
 pkg/core/store/index/service_consumer_metadata.go  |   4 +-
 pkg/core/store/index/service_provider_metadata.go  |   4 +-
 pkg/core/store/store.go                            |   8 +-
 .../index/common.go => store/memory/factory.go}    |  35 +-
 pkg/store/memory/store.go                          | 203 ++++++
 pkg/store/memory/store_test.go                     | 777 +++++++++++++++++++++
 20 files changed, 1046 insertions(+), 70 deletions(-)

diff --git a/app/dubbo-admin/dubbo-admin.yaml b/app/dubbo-admin/dubbo-admin.yaml
index 8f105ffa..6147444d 100644
--- a/app/dubbo-admin/dubbo-admin.yaml
+++ b/app/dubbo-admin/dubbo-admin.yaml
@@ -37,8 +37,7 @@ console:
     password: dubbo@2025
     expirationTime: 3600
 store:
-  type: mysql
-  address: xx
+  type: memory
 discovery:
   - type: nacos
     id: nacos-44.33
diff --git a/pkg/common/errors/error.go b/pkg/common/bizerror/common.go
similarity index 98%
rename from pkg/common/errors/error.go
rename to pkg/common/bizerror/common.go
index 19638601..1f993c4b 100644
--- a/pkg/common/errors/error.go
+++ b/pkg/common/bizerror/common.go
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package errors
+package bizerror
 
 import (
        "errors"
diff --git a/pkg/console/handler/configurator_rule.go 
b/pkg/console/handler/configurator_rule.go
index 37ed7f80..19280c5e 100644
--- a/pkg/console/handler/configurator_rule.go
+++ b/pkg/console/handler/configurator_rule.go
@@ -48,7 +48,7 @@ func ConfiguratorSearch(ctx consolectx.Context) 
gin.HandlerFunc {
                        pageData, err = 
manager.PageListByIndexes[*meshresource.DynamicConfigResource](
                                ctx.ResourceManager(),
                                meshresource.DynamicConfigKind,
-                               map[string]interface{}{
+                               map[string]string{
                                        index.ByMeshIndex: req.Mesh,
                                },
                                req.PageReq,
diff --git a/pkg/console/handler/tag_rule.go b/pkg/console/handler/tag_rule.go
index 6bc1ab89..7f5756fc 100644
--- a/pkg/console/handler/tag_rule.go
+++ b/pkg/console/handler/tag_rule.go
@@ -49,7 +49,7 @@ func TagRuleSearch(ctx consolectx.Context) gin.HandlerFunc {
                        pageData, err = 
manager.PageListByIndexes[*meshresource.TagRouteResource](
                                ctx.ResourceManager(),
                                meshresource.TagRouteKind,
-                               map[string]interface{}{
+                               map[string]string{
                                        index.ByMeshIndex: req.Mesh,
                                },
                                req.PageReq)
diff --git a/pkg/console/service/application.go 
b/pkg/console/service/application.go
index ff7f3500..d78f512a 100644
--- a/pkg/console/service/application.go
+++ b/pkg/console/service/application.go
@@ -35,7 +35,7 @@ func GetApplicationDetail(ctx consolectx.Context, req 
*model.ApplicationDetailRe
        instanceResources, err := 
manager.ListByIndexes[*meshresource.InstanceResource](
                ctx.ResourceManager(),
                meshresource.InstanceKind,
-               map[string]interface{}{
+               map[string]string{
                        index.ByMeshIndex:            req.Mesh,
                        index.ByInstanceAppNameIndex: req.AppName,
                },
@@ -61,7 +61,7 @@ func GetAppInstanceInfo(ctx consolectx.Context, req 
*model.ApplicationTabInstanc
        pageData, err := 
manager.PageListByIndexes[*meshresource.InstanceResource](
                ctx.ResourceManager(),
                meshresource.InstanceKind,
-               map[string]interface{}{
+               map[string]string{
                        index.ByMeshIndex:            req.Mesh,
                        index.ByInstanceAppNameIndex: req.AppName,
                },
@@ -117,7 +117,7 @@ func getAppProvideServiceInfo(ctx consolectx.Context, req 
*model.ApplicationServ
        pageData, err := 
manager.PageListByIndexes[*meshresource.ServiceProviderMetadataResource](
                ctx.ResourceManager(),
                meshresource.ServiceProviderMetadataKind,
-               map[string]interface{}{
+               map[string]string{
                        index.ByMeshIndex:              req.Mesh,
                        index.ByServiceProviderAppName: req.AppName,
                },
@@ -160,7 +160,7 @@ func getAppConsumeServiceInfo(ctx consolectx.Context, req 
*model.ApplicationServ
        pageData, err := 
manager.PageListByIndexes[*meshresource.ServiceConsumerMetadataResource](
                ctx.ResourceManager(),
                meshresource.ServiceConsumerMetadataKind,
-               map[string]interface{}{
+               map[string]string{
                        index.ByMeshIndex:              req.Mesh,
                        index.ByServiceConsumerAppName: req.AppName,
                },
@@ -239,7 +239,7 @@ func searchApplications(
                pageData, err = 
manager.PageListByIndexes[*meshresource.ApplicationResource](
                        ctx.ResourceManager(),
                        meshresource.ApplicationKind,
-                       map[string]interface{}{
+                       map[string]string{
                                index.ByMeshIndex: mesh,
                        },
                        pageReq,
diff --git a/pkg/console/service/instance.go b/pkg/console/service/instance.go
index 111a8264..2686390d 100644
--- a/pkg/console/service/instance.go
+++ b/pkg/console/service/instance.go
@@ -53,7 +53,7 @@ func SearchInstances(ctx consolectx.Context, req 
*model.SearchInstanceReq) (*mod
                pageData, err = 
manager.PageListByIndexes[*meshresource.InstanceResource](
                        ctx.ResourceManager(),
                        meshresource.InstanceKind,
-                       map[string]interface{}{
+                       map[string]string{
                                index.ByMeshIndex: req.Mesh,
                        },
                        req.PageReq)
diff --git a/pkg/console/service/service.go b/pkg/console/service/service.go
index 73f079e6..4ff11e88 100644
--- a/pkg/console/service/service.go
+++ b/pkg/console/service/service.go
@@ -47,7 +47,7 @@ func SearchServices(ctx consolectx.Context, req 
*model.ServiceSearchReq) (*model
                pageData, err = 
manager.PageListByIndexes[*meshresource.ServiceResource](
                        ctx.ResourceManager(),
                        meshresource.ServiceKind,
-                       map[string]interface{}{
+                       map[string]string{
                                index.ByMeshIndex: req.Mesh,
                        },
                        req.PageReq,
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index 0b8329f2..213c817f 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -23,14 +23,8 @@ import (
        "github.com/pkg/errors"
 
        "github.com/apache/dubbo-admin/pkg/config/app"
-       _ "github.com/apache/dubbo-admin/pkg/console"
-       _ "github.com/apache/dubbo-admin/pkg/core/discovery"
-       _ "github.com/apache/dubbo-admin/pkg/core/engine"
-       _ "github.com/apache/dubbo-admin/pkg/core/events"
        "github.com/apache/dubbo-admin/pkg/core/logger"
-       _ "github.com/apache/dubbo-admin/pkg/core/manager"
        "github.com/apache/dubbo-admin/pkg/core/runtime"
-       _ "github.com/apache/dubbo-admin/pkg/core/store"
        "github.com/apache/dubbo-admin/pkg/diagnostics"
 )
 
diff --git a/pkg/store/memory/memory.go b/pkg/core/bootstrap/init.go
similarity index 67%
rename from pkg/store/memory/memory.go
rename to pkg/core/bootstrap/init.go
index 80a2c7cd..2fecda05 100644
--- a/pkg/store/memory/memory.go
+++ b/pkg/core/bootstrap/init.go
@@ -15,8 +15,14 @@
  * limitations under the License.
  */
 
-package memory
+package bootstrap
 
-import _ "k8s.io/client-go/tools/cache"
-
-// TODO implement memory resource store, refer to client-go cache.Store
+import (
+       _ "github.com/apache/dubbo-admin/pkg/console"
+       _ "github.com/apache/dubbo-admin/pkg/core/discovery"
+       _ "github.com/apache/dubbo-admin/pkg/core/engine"
+       _ "github.com/apache/dubbo-admin/pkg/core/events"
+       _ "github.com/apache/dubbo-admin/pkg/core/manager"
+       _ "github.com/apache/dubbo-admin/pkg/core/store"
+       _ "github.com/apache/dubbo-admin/pkg/store/memory"
+)
diff --git a/pkg/core/manager/manager.go b/pkg/core/manager/manager.go
index 3cf8685e..2ed0a11f 100644
--- a/pkg/core/manager/manager.go
+++ b/pkg/core/manager/manager.go
@@ -28,9 +28,9 @@ type ReadOnlyResourceManager interface {
        // GetByKey returns the resource with the given resource key
        GetByKey(rk model.ResourceKind, key string) (r model.Resource, exist 
bool, err error)
        // ListByIndexes returns the resources with the given indexes, indexes 
is a map of index name and index value
-       ListByIndexes(rk model.ResourceKind, indexes map[string]interface{}) 
([]model.Resource, error)
+       ListByIndexes(rk model.ResourceKind, indexes map[string]string) 
([]model.Resource, error)
        // PageListByIndexes page list the resources with the given indexes, 
indexes is a map of index name and index value
-       PageListByIndexes(rk model.ResourceKind, indexes 
map[string]interface{}, pr model.PageReq) (*model.PageData[model.Resource], 
error)
+       PageListByIndexes(rk model.ResourceKind, indexes map[string]string, pr 
model.PageReq) (*model.PageData[model.Resource], error)
        // PageSearchResourceByConditions page fuzzy search resource by 
conditions, conditions cannot be empty
        // TODO support multiple conditions
        PageSearchResourceByConditions(rk model.ResourceKind, conditions 
[]string, pr model.PageReq) (*model.PageData[model.Resource], error)
@@ -74,7 +74,7 @@ func (rm *resourcesManager) GetByKey(rk model.ResourceKind, 
key string) (r model
        return item.(model.Resource), exist, err
 }
 
-func (rm *resourcesManager) ListByIndexes(rk model.ResourceKind, indexes 
map[string]interface{}) ([]model.Resource, error) {
+func (rm *resourcesManager) ListByIndexes(rk model.ResourceKind, indexes 
map[string]string) ([]model.Resource, error) {
        rs, err := rm.StoreRouter.ResourceKindRoute(rk)
        if err != nil {
                return nil, err
@@ -88,7 +88,7 @@ func (rm *resourcesManager) ListByIndexes(rk 
model.ResourceKind, indexes map[str
 
 func (rm *resourcesManager) PageListByIndexes(
        rk model.ResourceKind,
-       indexes map[string]interface{},
+       indexes map[string]string,
        pr model.PageReq) (*model.PageData[model.Resource], error) {
 
        rs, err := rm.StoreRouter.ResourceKindRoute(rk)
diff --git a/pkg/core/manager/manager_helper.go 
b/pkg/core/manager/manager_helper.go
index 7429477f..ff32ce2a 100644
--- a/pkg/core/manager/manager_helper.go
+++ b/pkg/core/manager/manager_helper.go
@@ -20,7 +20,7 @@ package manager
 import (
        "reflect"
 
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
        "github.com/apache/dubbo-admin/pkg/core/resource/model"
 )
 
@@ -35,13 +35,13 @@ func GetByKey[T model.Resource](rm ReadOnlyResourceManager, 
rk model.ResourceKin
        typedResource, ok := resource.(T)
        if !ok {
                var zero T
-               return zero, false, errors.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
+               return zero, false, bizerror.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
        }
        return typedResource, true, nil
 }
 
 // ListByIndexes is a helper function of ResourceManager.ListByIndexes
-func ListByIndexes[T model.Resource](rm ReadOnlyResourceManager, rk 
model.ResourceKind, indexes map[string]interface{}) ([]T, error) {
+func ListByIndexes[T model.Resource](rm ReadOnlyResourceManager, rk 
model.ResourceKind, indexes map[string]string) ([]T, error) {
        resources, err := rm.ListByIndexes(rk, indexes)
        if err != nil {
                return nil, err
@@ -51,7 +51,7 @@ func ListByIndexes[T model.Resource](rm 
ReadOnlyResourceManager, rk model.Resour
        for i, resource := range resources {
                typedResource, ok := resource.(T)
                if !ok {
-                       return nil, errors.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
+                       return nil, bizerror.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
                }
                typedResources[i] = typedResource
        }
@@ -63,7 +63,7 @@ func ListByIndexes[T model.Resource](rm 
ReadOnlyResourceManager, rk model.Resour
 func PageListByIndexes[T model.Resource](
        rm ReadOnlyResourceManager,
        rk model.ResourceKind,
-       indexes map[string]interface{},
+       indexes map[string]string,
        pr model.PageReq) (*model.PageData[T], error) {
 
        pageData, err := rm.PageListByIndexes(rk, indexes, pr)
@@ -75,7 +75,7 @@ func PageListByIndexes[T model.Resource](
        for i, resource := range pageData.Data {
                typedResource, ok := resource.(T)
                if !ok {
-                       return nil, errors.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
+                       return nil, bizerror.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
                }
                typedResources[i] = typedResource
        }
@@ -105,7 +105,7 @@ func PageSearchResourceByConditions[T model.Resource](
        for i, resource := range pageData.Data {
                typedResource, ok := resource.(T)
                if !ok {
-                       return nil, errors.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
+                       return nil, bizerror.NewAssertionError(rk, 
reflect.TypeOf(typedResource).Name())
                }
                typedResources[i] = typedResource
        }
diff --git a/pkg/core/store/factory.go b/pkg/core/store/factory.go
index 09202b23..e0e9b79e 100644
--- a/pkg/core/store/factory.go
+++ b/pkg/core/store/factory.go
@@ -20,7 +20,7 @@ package store
 import (
        "fmt"
 
-       "github.com/apache/dubbo-admin/pkg/config/store"
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
        "github.com/apache/dubbo-admin/pkg/core/resource/model"
 )
 
@@ -37,13 +37,13 @@ func FactoryRegistry() Registry {
 // Factory is the interface for create a specific type of ManagedResourceStore
 type Factory interface {
        // Support returns true if the factory supports the given type in config
-       Support(store.Type) bool
+       Support(storecfg.Type) bool
        // New returns a new ManagedResourceStore for the model.ResourceKind 
using the given config
-       New(model.ResourceKind, *store.Config) (ManagedResourceStore, error)
+       New(model.ResourceKind, *storecfg.Config) (ManagedResourceStore, error)
 }
 
 type Registry interface {
-       GetStoreFactory(store.Type) (Factory, error)
+       GetStoreFactory(storecfg.Type) (Factory, error)
 }
 
 type RegistryMutator interface {
@@ -66,7 +66,7 @@ func newStoreFactoryRegistry() MutableRegistry {
                factories: make([]Factory, 0),
        }
 }
-func (s *storeFactoryRegistry) GetStoreFactory(t store.Type) (Factory, error) {
+func (s *storeFactoryRegistry) GetStoreFactory(t storecfg.Type) (Factory, 
error) {
        for _, factory := range s.factories {
                if factory.Support(t) {
                        return factory, nil
diff --git a/pkg/core/store/index/common.go b/pkg/core/store/index/common.go
index 240bb1e9..f2b69f96 100644
--- a/pkg/core/store/index/common.go
+++ b/pkg/core/store/index/common.go
@@ -23,7 +23,7 @@ import (
        "github.com/duke-git/lancet/v2/slice"
        "k8s.io/client-go/tools/cache"
 
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
        coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
 )
 
@@ -41,7 +41,7 @@ func init() {
 func ByMesh(obj interface{}) ([]string, error) {
        r, ok := obj.(coremodel.Resource)
        if !ok {
-               return nil, errors.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+               return nil, bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
        }
        return []string{r.MeshName()}, nil
 }
diff --git a/pkg/core/store/index/instance.go b/pkg/core/store/index/instance.go
index e2d4c86f..f0bbd3fd 100644
--- a/pkg/core/store/index/instance.go
+++ b/pkg/core/store/index/instance.go
@@ -22,7 +22,7 @@ import (
 
        "k8s.io/client-go/tools/cache"
 
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
        meshresource 
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
 )
 
@@ -41,7 +41,7 @@ func init() {
 func byInstanceAppName(obj interface{}) ([]string, error) {
        instance, ok := obj.(*meshresource.InstanceResource)
        if !ok {
-               return nil, errors.NewAssertionError(meshresource.InstanceKind, 
reflect.TypeOf(obj).Name())
+               return nil, 
bizerror.NewAssertionError(meshresource.InstanceKind, 
reflect.TypeOf(obj).Name())
        }
        if instance.Spec == nil {
                return []string{}, nil
@@ -52,7 +52,7 @@ func byInstanceAppName(obj interface{}) ([]string, error) {
 func byIp(obj interface{}) ([]string, error) {
        instance, ok := obj.(*meshresource.InstanceResource)
        if !ok {
-               return nil, errors.NewAssertionError(meshresource.InstanceKind, 
reflect.TypeOf(obj).Name())
+               return nil, 
bizerror.NewAssertionError(meshresource.InstanceKind, 
reflect.TypeOf(obj).Name())
        }
        if instance.Spec == nil {
                return []string{}, nil
diff --git a/pkg/core/store/index/service_consumer_metadata.go 
b/pkg/core/store/index/service_consumer_metadata.go
index a5ce6eed..6b5c7fa3 100644
--- a/pkg/core/store/index/service_consumer_metadata.go
+++ b/pkg/core/store/index/service_consumer_metadata.go
@@ -22,7 +22,7 @@ import (
 
        "k8s.io/client-go/tools/cache"
 
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
        meshresource 
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
 )
 
@@ -39,7 +39,7 @@ func init() {
 func byServiceConsumerAppName(obj interface{}) ([]string, error) {
        metadata, ok := obj.(*meshresource.ServiceConsumerMetadataResource)
        if !ok {
-               return nil, 
errors.NewAssertionError(meshresource.ServiceConsumerMetadataKind, 
reflect.TypeOf(obj).Name())
+               return nil, 
bizerror.NewAssertionError(meshresource.ServiceConsumerMetadataKind, 
reflect.TypeOf(obj).Name())
        }
        if metadata == nil {
                return []string{}, nil
diff --git a/pkg/core/store/index/service_provider_metadata.go 
b/pkg/core/store/index/service_provider_metadata.go
index 5975150f..53f9b892 100644
--- a/pkg/core/store/index/service_provider_metadata.go
+++ b/pkg/core/store/index/service_provider_metadata.go
@@ -22,7 +22,7 @@ import (
 
        "k8s.io/client-go/tools/cache"
 
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
        meshresource 
"github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
 )
 
@@ -39,7 +39,7 @@ func init() {
 func byServiceProviderAppName(obj interface{}) ([]string, error) {
        metadata, ok := obj.(*meshresource.ServiceProviderMetadataResource)
        if !ok {
-               return nil, 
errors.NewAssertionError(meshresource.ServiceProviderMetadataKind, 
reflect.TypeOf(obj))
+               return nil, 
bizerror.NewAssertionError(meshresource.ServiceProviderMetadataKind, 
reflect.TypeOf(obj).Name())
        }
        if metadata.Spec == nil {
                return []string{}, nil
diff --git a/pkg/core/store/store.go b/pkg/core/store/store.go
index 04fc8cf9..15d2117e 100644
--- a/pkg/core/store/store.go
+++ b/pkg/core/store/store.go
@@ -33,9 +33,13 @@ import (
 // ResourceStore expanded the interface of cache.Indexer and cache.Store
 type ResourceStore interface {
        Indexer
-       ListByIndexes(indexes map[string]interface{}) ([]model.Resource, error)
+       // GetByKeys get resources by keys, return list of resource.
+       // if a resource of specified key doesn't exist in the store, resource 
list will not include it
+       GetByKeys(keys []string) ([]model.Resource, error)
+       // ListByIndexes list resources by indexes, indexes is map of index 
name and index value
+       ListByIndexes(indexes map[string]string) ([]model.Resource, error)
        // PageListByIndexes list resources by indexes pageable, indexes is map 
of index name and index value
-       PageListByIndexes(indexes map[string]interface{}, pq model.PageReq) 
(*model.PageData[model.Resource], error)
+       PageListByIndexes(indexes map[string]string, pq model.PageReq) 
(*model.PageData[model.Resource], error)
 }
 
 // ManagedResourceStore includes both functional interfaces and lifecycle 
interfaces
diff --git a/pkg/core/store/index/common.go b/pkg/store/memory/factory.go
similarity index 60%
copy from pkg/core/store/index/common.go
copy to pkg/store/memory/factory.go
index 240bb1e9..564b32d1 100644
--- a/pkg/core/store/index/common.go
+++ b/pkg/store/memory/factory.go
@@ -15,33 +15,26 @@
  * limitations under the License.
  */
 
-package index
+package memory
 
 import (
-       "reflect"
-
-       "github.com/duke-git/lancet/v2/slice"
-       "k8s.io/client-go/tools/cache"
-
-       "github.com/apache/dubbo-admin/pkg/common/errors"
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
        coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/store"
 )
 
-const ByMeshIndex = "idx_mesh"
-
 func init() {
-       rks := coremodel.ResourceSchemaRegistry().AllResourceKinds()
-       slice.ForEach(rks, func(_ int, rk coremodel.ResourceKind) {
-               RegisterIndexers(rk, map[string]cache.IndexFunc{
-                       ByMeshIndex: ByMesh,
-               })
-       })
+       store.RegisterFactory(&storeFactory{})
+}
+
+type storeFactory struct{}
+
+var _ store.Factory = &storeFactory{}
+
+func (sf *storeFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.Memory
 }
 
-func ByMesh(obj interface{}) ([]string, error) {
-       r, ok := obj.(coremodel.Resource)
-       if !ok {
-               return nil, errors.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
-       }
-       return []string{r.MeshName()}, nil
+func (sf *storeFactory) New(_ coremodel.ResourceKind, _ *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMemoryResourceStore(), nil
 }
diff --git a/pkg/store/memory/store.go b/pkg/store/memory/store.go
new file mode 100644
index 00000000..e522cb06
--- /dev/null
+++ b/pkg/store/memory/store.go
@@ -0,0 +1,203 @@
+/*
+ * 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 memory
+
+import (
+       "reflect"
+       "sort"
+
+       set "github.com/duke-git/lancet/v2/datastructure/set"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/util/slices"
+       coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+type resourceStore struct {
+       storeProxy cache.Indexer
+}
+
+var _ store.ManagedResourceStore = &resourceStore{}
+
+func NewMemoryResourceStore() store.ManagedResourceStore {
+       return &resourceStore{}
+}
+
+func (rs *resourceStore) Init(_ runtime.BuilderContext) error {
+       rs.storeProxy = cache.NewIndexer(
+               func(obj interface{}) (string, error) {
+                       r, ok := obj.(coremodel.Resource)
+                       if !ok {
+                               return "", 
bizerror.NewAssertionError("Resource", reflect.TypeOf(obj).Name())
+                       }
+                       return r.ResourceKey(), nil
+               },
+               cache.Indexers{},
+       )
+       return nil
+}
+
+func (rs *resourceStore) Start(_ runtime.Runtime, _ <-chan struct{}) error {
+       return nil
+}
+
+func (rs *resourceStore) Add(obj interface{}) error {
+       return rs.storeProxy.Add(obj)
+}
+
+func (rs *resourceStore) Update(obj interface{}) error {
+       return rs.storeProxy.Update(obj)
+}
+
+func (rs *resourceStore) Delete(obj interface{}) error {
+       return rs.storeProxy.Delete(obj)
+}
+
+func (rs *resourceStore) List() []interface{} {
+       return rs.storeProxy.List()
+}
+
+func (rs *resourceStore) ListKeys() []string {
+       return rs.storeProxy.ListKeys()
+}
+
+func (rs *resourceStore) Get(obj interface{}) (item interface{}, exists bool, 
err error) {
+       return rs.storeProxy.Get(obj)
+}
+
+func (rs *resourceStore) GetByKey(key string) (item interface{}, exists bool, 
err error) {
+       return rs.storeProxy.GetByKey(key)
+}
+
+func (rs *resourceStore) Replace(i []interface{}, s string) error {
+       return rs.storeProxy.Replace(i, s)
+}
+
+func (rs *resourceStore) Resync() error {
+       return rs.storeProxy.Resync()
+}
+
+func (rs *resourceStore) Index(indexName string, obj interface{}) 
([]interface{}, error) {
+       return rs.storeProxy.Index(indexName, obj)
+}
+
+func (rs *resourceStore) IndexKeys(indexName, indexedValue string) ([]string, 
error) {
+       return rs.storeProxy.IndexKeys(indexName, indexedValue)
+}
+
+func (rs *resourceStore) ListIndexFuncValues(indexName string) []string {
+       return rs.storeProxy.ListIndexFuncValues(indexName)
+}
+
+func (rs *resourceStore) ByIndex(indexName, indexedValue string) 
([]interface{}, error) {
+       return rs.storeProxy.ByIndex(indexName, indexedValue)
+}
+
+func (rs *resourceStore) GetIndexers() cache.Indexers {
+       return rs.storeProxy.GetIndexers()
+}
+
+func (rs *resourceStore) AddIndexers(newIndexers cache.Indexers) error {
+       return rs.storeProxy.AddIndexers(newIndexers)
+}
+
+func (rs *resourceStore) GetByKeys(keys []string) ([]coremodel.Resource, 
error) {
+       resources := make([]coremodel.Resource, 0)
+       for _, key := range keys {
+               r, exists, err := rs.storeProxy.GetByKey(key)
+               if err != nil {
+                       return nil, err
+               }
+               if !exists {
+                       continue
+               }
+               res, ok := r.(coremodel.Resource)
+               if !ok {
+                       return nil, bizerror.NewAssertionError("Resource", 
reflect.TypeOf(r).Name())
+               }
+               resources = append(resources, res)
+       }
+       return resources, nil
+}
+
+func (rs *resourceStore) ListByIndexes(indexes map[string]string) 
([]coremodel.Resource, error) {
+       keys, err := rs.getKeysByIndexes(indexes)
+       if err != nil {
+               return nil, err
+       }
+       resources, err := rs.GetByKeys(keys)
+       if err != nil {
+               return nil, err
+       }
+       resources = slices.SortBy(resources, func(r coremodel.Resource) string {
+               return r.ResourceKey()
+       })
+       return resources, nil
+}
+
+func (rs *resourceStore) PageListByIndexes(indexes map[string]string, pq 
coremodel.PageReq) (*coremodel.PageData[coremodel.Resource], error) {
+       keys, err := rs.getKeysByIndexes(indexes)
+       if err != nil {
+               return nil, err
+       }
+       sort.Strings(keys)
+       total := len(keys)
+       resources := make([]coremodel.Resource, 0, pq.PageSize)
+       for i := pq.PageOffset; len(resources) < pq.PageSize && i < total; i++ {
+               r, exists, err := rs.storeProxy.GetByKey(keys[i])
+               if err != nil {
+                       return nil, err
+               }
+               if !exists {
+                       total -= 1
+                       continue
+               }
+               res, ok := r.(coremodel.Resource)
+               if !ok {
+                       return nil, bizerror.NewAssertionError("Resource", 
reflect.TypeOf(r).Name())
+               }
+               resources = append(resources, res)
+       }
+       pageData := coremodel.NewPageData(total, pq.PageOffset, pq.PageSize, 
resources)
+       return pageData, nil
+}
+
+func (rs *resourceStore) getKeysByIndexes(indexes map[string]string) 
([]string, error) {
+       if len(indexes) == 0 {
+               return []string{}, nil
+       }
+       keySet := set.New[string]()
+       first := true
+       for indexName, indexValue := range indexes {
+               keys, err := rs.storeProxy.IndexKeys(indexName, indexValue)
+               if err != nil {
+                       return nil, err
+               }
+               if first {
+                       keySet = set.FromSlice(keys)
+                       first = false
+               } else {
+                       nextSet := set.FromSlice(keys)
+                       keySet = keySet.Intersection(nextSet)
+               }
+       }
+       return keySet.ToSlice(), nil
+}
diff --git a/pkg/store/memory/store_test.go b/pkg/store/memory/store_test.go
new file mode 100644
index 00000000..f78c88c0
--- /dev/null
+++ b/pkg/store/memory/store_test.go
@@ -0,0 +1,777 @@
+/*
+ * 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 memory
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+)
+
+// mockResource is a mock implementation of model.Resource for testing
+type mockResource struct {
+       kind      model.ResourceKind
+       key       string
+       mesh      string
+       meta      metav1.ObjectMeta
+       spec      model.ResourceSpec
+       objectRef runtime.Object
+}
+
+func (mr *mockResource) GetObjectKind() schema.ObjectKind {
+       return schema.EmptyObjectKind
+}
+
+func (mr *mockResource) DeepCopyObject() runtime.Object {
+       return mr.objectRef
+}
+
+func (mr *mockResource) ResourceKind() model.ResourceKind {
+       return mr.kind
+}
+
+func (mr *mockResource) ResourceKey() string {
+       return mr.key
+}
+
+func (mr *mockResource) MeshName() string {
+       return mr.mesh
+}
+
+func (mr *mockResource) ResourceMeta() metav1.ObjectMeta {
+       return mr.meta
+}
+
+func (mr *mockResource) ResourceSpec() model.ResourceSpec {
+       return mr.spec
+}
+
+func TestNewMemoryResourceStore(t *testing.T) {
+       store := NewMemoryResourceStore()
+       assert.NotNil(t, store)
+}
+
+func TestResourceStore_Init(t *testing.T) {
+       store := &resourceStore{}
+       err := store.Init(nil)
+       assert.NoError(t, err)
+       assert.NotNil(t, store.storeProxy)
+}
+
+func TestResourceStore_AddAndGet(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create a mock resource
+       mockRes := &mockResource{
+               kind: "TestResource",
+               key:  "test-key",
+               mesh: "default",
+               meta: metav1.ObjectMeta{
+                       Name:      "test-resource",
+                       Namespace: "default",
+               },
+       }
+
+       // Add the resource
+       err = store.Add(mockRes)
+       assert.NoError(t, err)
+
+       // Get the resource
+       item, exists, err := store.Get(mockRes)
+       assert.NoError(t, err)
+       assert.True(t, exists)
+       assert.Equal(t, mockRes, item)
+
+       // Get by key
+       item, exists, err = store.GetByKey("test-key")
+       assert.NoError(t, err)
+       assert.True(t, exists)
+       assert.Equal(t, mockRes, item)
+}
+
+func TestResourceStore_Update(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create a mock resource
+       mockRes := &mockResource{
+               kind: "TestResource",
+               key:  "test-key",
+               mesh: "default",
+               meta: metav1.ObjectMeta{
+                       Name:      "test-resource",
+                       Namespace: "default",
+               },
+       }
+
+       // Add the resource
+       err = store.Add(mockRes)
+       assert.NoError(t, err)
+
+       // Update the resource
+       updatedRes := &mockResource{
+               kind: "TestResource",
+               key:  "test-key",
+               mesh: "default",
+               meta: metav1.ObjectMeta{
+                       Name:      "updated-resource",
+                       Namespace: "default",
+               },
+       }
+
+       err = store.Update(updatedRes)
+       assert.NoError(t, err)
+
+       // Get the updated resource
+       item, exists, err := store.Get(updatedRes)
+       assert.NoError(t, err)
+       assert.True(t, exists)
+       assert.Equal(t, updatedRes, item)
+       assert.Equal(t, "updated-resource", item.(*mockResource).meta.Name)
+}
+
+func TestResourceStore_Delete(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create a mock resource
+       mockRes := &mockResource{
+               kind: "TestResource",
+               key:  "test-key",
+               mesh: "default",
+               meta: metav1.ObjectMeta{
+                       Name:      "test-resource",
+                       Namespace: "default",
+               },
+       }
+
+       // Add the resource
+       err = store.Add(mockRes)
+       assert.NoError(t, err)
+
+       // Delete the resource
+       err = store.Delete(mockRes)
+       assert.NoError(t, err)
+
+       // Try to get the deleted resource
+       _, exists, err := store.Get(mockRes)
+       assert.NoError(t, err)
+       assert.False(t, exists)
+}
+
+func TestResourceStore_List(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-1",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-2",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+
+       // List resources
+       list := store.List()
+       assert.Len(t, list, 2)
+       assert.Contains(t, list, mockRes1)
+       assert.Contains(t, list, mockRes2)
+}
+
+func TestResourceStore_ListKeys(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-1",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-2",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+
+       // List keys
+       keys := store.ListKeys()
+       assert.Len(t, keys, 2)
+       assert.Contains(t, keys, "test-key-1")
+       assert.Contains(t, keys, "test-key-2")
+}
+
+func TestResourceStore_Replace(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create initial mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-1",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-2",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+
+       // Replace with new set of resources
+       newResources := []interface{}{mockRes2}
+       err = store.Replace(newResources, "version-1")
+       assert.NoError(t, err)
+
+       // Check that only the new resource exists
+       keys := store.ListKeys()
+       assert.Len(t, keys, 1)
+       assert.Contains(t, keys, "test-key-2")
+       assert.NotContains(t, keys, "test-key-1")
+}
+
+func TestResourceStore_GetByKeys(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-1",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "test-key-2",
+               mesh: "default",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       // Add only first resource
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+
+       // Get by multiple keys
+       keys := []string{"test-key-1", "test-key-2", "test-key-3"}
+       resources, err := store.GetByKeys(keys)
+       assert.NoError(t, err)
+       assert.Len(t, resources, 2)
+       assert.Equal(t, mockRes1, resources[0])
+       assert.Equal(t, mockRes2, resources[1])
+}
+
+func TestResourceStore_ListByIndexes(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/test-key-1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/test-key-2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       mockRes3 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh2/test-key-3",
+               mesh: "mesh2",
+               meta: metav1.ObjectMeta{Name: "test-resource-3"},
+       }
+
+       // Add indexers
+       indexers := map[string]cache.IndexFunc{
+               "by-mesh": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       return []string{resource.MeshName()}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+       err = store.Add(mockRes3)
+       assert.NoError(t, err)
+
+       // List by indexes
+       indexes := map[string]string{"by-mesh": "mesh1"}
+       resources, err := store.ListByIndexes(indexes)
+       assert.NoError(t, err)
+       assert.Len(t, resources, 2)
+       // Should be sorted by ResourceKey
+       assert.Equal(t, mockRes1, resources[0])
+       assert.Equal(t, mockRes2, resources[1])
+}
+
+func TestResourceStore_PageListByIndexes(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/test-key-1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{Name: "test-resource-1"},
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/test-key-2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{Name: "test-resource-2"},
+       }
+
+       mockRes3 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/test-key-3",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{Name: "test-resource-3"},
+       }
+
+       // Add indexers
+       indexers := map[string]cache.IndexFunc{
+               "by-mesh": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       return []string{resource.MeshName()}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+       err = store.Add(mockRes3)
+       assert.NoError(t, err)
+
+       // Page list by indexes
+       indexes := map[string]string{"by-mesh": "mesh1"}
+       pageReq := model.PageReq{
+               PageOffset: 0,
+               PageSize:   2,
+       }
+       pageData, err := store.PageListByIndexes(indexes, pageReq)
+       assert.NoError(t, err)
+       // Total 3 resources
+       assert.Equal(t, 3, pageData.Total)
+       // Page offset 0
+       assert.Equal(t, 0, pageData.PageOffset)
+       // Page size 2
+       assert.Equal(t, 2, pageData.PageSize)
+       // 2 items in this page
+       assert.Len(t, pageData.Data, 2)
+       // Sorted by key
+       assert.Equal(t, mockRes1, pageData.Data[0])
+       assert.Equal(t, mockRes2, pageData.Data[1])
+
+       // Second page
+       pageReq.PageOffset = 2
+       pageData, err = store.PageListByIndexes(indexes, pageReq)
+       assert.NoError(t, err)
+       // Total still 3
+       assert.Equal(t, 3, pageData.Total)
+       // Page offset 2
+       assert.Equal(t, 2, pageData.PageOffset)
+       // Page size 2
+       assert.Equal(t, 2, pageData.PageSize)
+       // Only 1 item left
+       assert.Len(t, pageData.Data, 1)
+       // Last item
+       assert.Equal(t, mockRes3, pageData.Data[0])
+}
+
+func TestResourceStore_MultipleIndexes(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestApplication",
+               key:  "mesh1/app1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name:      "app1",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "env":     "prod",
+                       },
+               },
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestApplication",
+               key:  "mesh1/app2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name:      "app2",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "version": "v2",
+                               "env":     "prod",
+                       },
+               },
+       }
+
+       mockRes3 := &mockResource{
+               kind: "TestApplication",
+               key:  "mesh2/app3",
+               mesh: "mesh2",
+               meta: metav1.ObjectMeta{
+                       Name:      "app3",
+                       Namespace: "default",
+                       Labels: map[string]string{
+                               "version": "v1",
+                               "env":     "dev",
+                       },
+               },
+       }
+
+       // Add indexers for multiple fields
+       indexers := map[string]cache.IndexFunc{
+               "by-mesh": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       return []string{resource.MeshName()}, nil
+               },
+               "by-version": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       version := resource.ResourceMeta().Labels["version"]
+                       return []string{version}, nil
+               },
+               "by-env": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       env := resource.ResourceMeta().Labels["env"]
+                       return []string{env}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+       for _, res := range resources {
+               err = store.Add(res)
+               assert.NoError(t, err)
+       }
+
+       // Test multiple indexes - get all prod env resources in mesh1
+       indexes := map[string]string{
+               "by-mesh":    "mesh1",
+               "by-version": "v1",
+       }
+       result, err := store.ListByIndexes(indexes)
+       assert.NoError(t, err)
+       assert.Len(t, result, 1)
+       // Should contain app1
+       keys := make([]string, len(result))
+       for i, res := range result {
+               keys[i] = res.ResourceKey()
+       }
+       assert.Contains(t, keys, "mesh1/app1")
+}
+
+func TestResourceStore_IndexKeys(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestService",
+               key:  "mesh1/service1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "service1",
+                       Labels: map[string]string{
+                               "group": "frontend",
+                       },
+               },
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestService",
+               key:  "mesh1/service2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "service2",
+                       Labels: map[string]string{
+                               "group": "backend",
+                       },
+               },
+       }
+
+       mockRes3 := &mockResource{
+               kind: "TestService",
+               key:  "mesh1/service3",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "service3",
+                       Labels: map[string]string{
+                               "group": "frontend",
+                       },
+               },
+       }
+
+       // Add indexer
+       indexers := map[string]cache.IndexFunc{
+               "by-group": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       group := resource.ResourceMeta().Labels["group"]
+                       return []string{group}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+       for _, res := range resources {
+               err = store.Add(res)
+               assert.NoError(t, err)
+       }
+
+       // Test IndexKeys method
+       keys, err := store.IndexKeys("by-group", "frontend")
+       assert.NoError(t, err)
+       assert.Len(t, keys, 2)
+       assert.Contains(t, keys, "mesh1/service1")
+       assert.Contains(t, keys, "mesh1/service3")
+
+       keys, err = store.IndexKeys("by-group", "backend")
+       assert.NoError(t, err)
+       assert.Len(t, keys, 1)
+       assert.Contains(t, keys, "mesh1/service2")
+}
+
+func TestResourceStore_ByIndex(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestInstance",
+               key:  "mesh1/instance1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "instance1",
+                       Labels: map[string]string{
+                               "type": "web",
+                       },
+               },
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestInstance",
+               key:  "mesh1/instance2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "instance2",
+                       Labels: map[string]string{
+                               "type": "database",
+                       },
+               },
+       }
+
+       // Add indexer
+       indexers := map[string]cache.IndexFunc{
+               "by-type": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       instanceType := resource.ResourceMeta().Labels["type"]
+                       return []string{instanceType}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       err = store.Add(mockRes1)
+       assert.NoError(t, err)
+       err = store.Add(mockRes2)
+       assert.NoError(t, err)
+
+       // Test ByIndex method
+       items, err := store.ByIndex("by-type", "web")
+       assert.NoError(t, err)
+       assert.Len(t, items, 1)
+       assert.Equal(t, mockRes1, items[0])
+
+       items, err = store.ByIndex("by-type", "database")
+       assert.NoError(t, err)
+       assert.Len(t, items, 1)
+       assert.Equal(t, mockRes2, items[0])
+}
+
+func TestResourceStore_GetIndexers(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Initially no indexers
+       indexers := store.GetIndexers()
+       assert.Empty(t, indexers)
+
+       // Add indexers
+       newIndexers := map[string]cache.IndexFunc{
+               "by-name": func(obj interface{}) ([]string, error) {
+                       return 
[]string{obj.(model.Resource).ResourceMeta().Name}, nil
+               },
+               "by-kind": func(obj interface{}) ([]string, error) {
+                       return 
[]string{string(obj.(model.Resource).ResourceKind())}, nil
+               },
+       }
+       err = store.AddIndexers(newIndexers)
+       assert.NoError(t, err)
+
+       // Check if indexers were added
+       indexers = store.GetIndexers()
+       assert.Len(t, indexers, 2)
+       assert.Contains(t, indexers, "by-name")
+       assert.Contains(t, indexers, "by-kind")
+}
+
+func TestResourceStore_ListIndexFuncValues(t *testing.T) {
+       store := NewMemoryResourceStore()
+       err := store.Init(nil)
+       assert.NoError(t, err)
+
+       // Create mock resources
+       mockRes1 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/resource1",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "resource1",
+                       Labels: map[string]string{
+                               "status": "active",
+                       },
+               },
+       }
+
+       mockRes2 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/resource2",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "resource2",
+                       Labels: map[string]string{
+                               "status": "inactive",
+                       },
+               },
+       }
+
+       mockRes3 := &mockResource{
+               kind: "TestResource",
+               key:  "mesh1/resource3",
+               mesh: "mesh1",
+               meta: metav1.ObjectMeta{
+                       Name: "resource3",
+                       Labels: map[string]string{
+                               "status": "active",
+                       },
+               },
+       }
+
+       // Add indexer
+       indexers := map[string]cache.IndexFunc{
+               "by-status": func(obj interface{}) ([]string, error) {
+                       resource := obj.(model.Resource)
+                       status := resource.ResourceMeta().Labels["status"]
+                       return []string{status}, nil
+               },
+       }
+       err = store.AddIndexers(indexers)
+       assert.NoError(t, err)
+
+       // Add resources
+       resources := []model.Resource{mockRes1, mockRes2, mockRes3}
+       for _, res := range resources {
+               err = store.Add(res)
+               assert.NoError(t, err)
+       }
+
+       // Test ListIndexFuncValues method
+       values := store.ListIndexFuncValues("by-status")
+       assert.Len(t, values, 2)
+       assert.Contains(t, values, "active")
+       assert.Contains(t, values, "inactive")
+}


Reply via email to