You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by ti...@apache.org on 2020/01/19 06:39:56 UTC

[servicecomb-kie] branch master updated: SCB-1723 support pagination in kie apis (#71)

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

tianxiaoliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-kie.git


The following commit(s) were added to refs/heads/master by this push:
     new c943c7c  SCB-1723 support pagination in kie apis (#71)
c943c7c is described below

commit c943c7c0eed5fba98928cb46a690d3140a8fa647
Author: Ang Li <31...@users.noreply.github.com>
AuthorDate: Sun Jan 19 14:39:48 2020 +0800

    SCB-1723 support pagination in kie apis (#71)
    
    * SCB-1723 support pagination in kie apis
    
    * add slow query log, add db info to uri
    
    * SCB-1723 move offset and limit settings to functional options
    
    * SCB-1723 get paging params from query, no paging by default, add UTs
---
 .gitignore                                    |  1 +
 docs/configurations/storage.md                |  2 +-
 examples/dev/db.js                            |  3 +-
 examples/dev/kie-conf.yaml                    |  2 +-
 pkg/common/common.go                          |  2 ++
 server/resource/v1/common.go                  | 14 ++++++----
 server/resource/v1/doc_struct.go              | 12 ++++++++
 server/resource/v1/history_resource.go        | 16 +++++++++--
 server/resource/v1/kv_resource.go             | 40 +++++++++++++++++++--------
 server/resource/v1/kv_resource_test.go        | 36 +++++++++++++++++++++++-
 server/service/mongo/counter/revision_test.go |  2 +-
 server/service/mongo/history/dao.go           | 13 +++++++--
 server/service/mongo/history/service.go       |  2 +-
 server/service/mongo/history/service_test.go  |  2 +-
 server/service/mongo/kv/kv_dao.go             | 10 ++++++-
 server/service/mongo/kv/kv_service.go         | 14 ++++++++--
 server/service/mongo/kv/kv_test.go            | 29 +++++++++++--------
 server/service/options.go                     | 16 +++++++++++
 server/service/service.go                     |  2 +-
 19 files changed, 174 insertions(+), 44 deletions(-)

diff --git a/.gitignore b/.gitignore
index 172347d..43590f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,5 @@ release
 etc/data/
 etc/ssl/
 tmp/
+conf/
 glide.lock
diff --git a/docs/configurations/storage.md b/docs/configurations/storage.md
index 1828410..dd5abc3 100644
--- a/docs/configurations/storage.md
+++ b/docs/configurations/storage.md
@@ -20,7 +20,7 @@ you can use mongo db as kie server storage to save configuration
 ### Example
 ```yaml
 db:
-  uri: mongodb://kie:123@127.0.0.1:27017
+  uri: mongodb://kie:123@127.0.0.1:27017/kie
   poolSize: 10
   timeout:  5m
   sslEnabled: true
diff --git a/examples/dev/db.js b/examples/dev/db.js
index 17f6cef..918ee58 100644
--- a/examples/dev/db.js
+++ b/examples/dev/db.js
@@ -47,4 +47,5 @@ db.createCollection( "kv", {
         } }
 } );
 db.kv.createIndex({"id": 1}, { unique: true } );
-db.kv.createIndex({key: 1, label_id: 1,domain:1,project:1},{ unique: true });
\ No newline at end of file
+db.kv.createIndex({key: 1, label_id: 1,domain:1,project:1},{ unique: true });
+db.setProfilingLevel(1, {slowms: 80, sampleRate: 1} );
\ No newline at end of file
diff --git a/examples/dev/kie-conf.yaml b/examples/dev/kie-conf.yaml
index c9633fa..4fef6b2 100644
--- a/examples/dev/kie-conf.yaml
+++ b/examples/dev/kie-conf.yaml
@@ -1,6 +1,6 @@
 db:
   #uri: mongodb://rwuser:xxx@x.x.x.x:8635/test?authSource=admin
-  uri: mongodb://kie:123@127.0.0.1:27017
+  uri: mongodb://kie:123@127.0.0.1:27017/kie
   type: mongodb
   poolSize: 10
   timeout:  5m
diff --git a/pkg/common/common.go b/pkg/common/common.go
index 45739ad..41bd161 100644
--- a/pkg/common/common.go
+++ b/pkg/common/common.go
@@ -25,6 +25,8 @@ const (
 	QueryParamRev    = "revision"
 	QueryParamMatch  = "match"
 	QueryParamKeyID  = "kv_id"
+	QueryLimit       = "limit"
+	QueryOffset      = "offset"
 )
 
 //http headers
diff --git a/server/resource/v1/common.go b/server/resource/v1/common.go
index 85e3e41..8a329f0 100644
--- a/server/resource/v1/common.go
+++ b/server/resource/v1/common.go
@@ -205,21 +205,25 @@ func checkPagination(limitStr, offsetStr string) (int64, int64, error) {
 		if err != nil {
 			return 0, 0, errors.New("invalid offset number")
 		}
-		if offset < 1 {
+		if offset < 0 {
 			return 0, 0, errors.New("invalid offset number")
 		}
 	}
 	return limit, offset, err
 }
 func queryAndResponse(rctx *restful.Context,
-	domain interface{}, project string, key string, labels map[string]string, limit, offset int) {
+	domain interface{}, project string, key string, labels map[string]string, limit, offset int64) {
 	m := getMatchPattern(rctx)
-	opts := []service.FindOption{service.WithKey(key), service.WithLabels(labels)}
+	opts := []service.FindOption{
+		service.WithKey(key),
+		service.WithLabels(labels),
+		service.WithLimit(limit),
+		service.WithOffset(offset),
+	}
 	if m == PatternExact {
 		opts = append(opts, service.WithExactLabels())
 	}
-	kv, err := service.KVService.List(rctx.Ctx, domain.(string), project,
-		limit, offset, opts...)
+	kv, err := service.KVService.List(rctx.Ctx, domain.(string), project, opts...)
 	if err != nil {
 		if err == service.ErrKeyNotExists {
 			WriteErrResponse(rctx, http.StatusNotFound, err.Error(), common.ContentTypeText)
diff --git a/server/resource/v1/doc_struct.go b/server/resource/v1/doc_struct.go
index 47635ce..b8de013 100644
--- a/server/resource/v1/doc_struct.go
+++ b/server/resource/v1/doc_struct.go
@@ -89,6 +89,18 @@ var (
 		ParamType: goRestful.QueryParameterKind,
 		Desc:      "label pairs,for example &label=service:order&label=version:1.0.0",
 	}
+	DocQueryLimitParameters = &restful.Parameters{
+		DataType:  "string",
+		Name:      common.QueryLimit,
+		ParamType: goRestful.QueryParameterKind,
+		Desc:      "limit,for example &limit=10",
+	}
+	DocQueryOffsetParameters = &restful.Parameters{
+		DataType:  "string",
+		Name:      common.QueryOffset,
+		ParamType: goRestful.QueryParameterKind,
+		Desc:      "offset,for example &offset=10",
+	}
 )
 
 //swagger doc path params
diff --git a/server/resource/v1/history_resource.go b/server/resource/v1/history_resource.go
index 44c21fc..09d60e6 100644
--- a/server/resource/v1/history_resource.go
+++ b/server/resource/v1/history_resource.go
@@ -36,14 +36,24 @@ type HistoryResource struct {
 //GetRevisions search key only by label
 func (r *HistoryResource) GetRevisions(context *restful.Context) {
 	var err error
-	labelID := context.ReadPathParameter("key_id")
-	if labelID == "" {
+	keyID := context.ReadPathParameter("key_id")
+	limitStr := context.ReadQueryParameter("limit")
+	offsetStr := context.ReadQueryParameter("offset")
+	limit, offset, err := checkPagination(limitStr, offsetStr)
+	if err != nil {
+		WriteErrResponse(context, http.StatusBadRequest, err.Error(), common.ContentTypeText)
+		return
+	}
+	if keyID == "" {
 		openlogging.Error("key id is nil")
 		WriteErrResponse(context, http.StatusForbidden, "key_id must not be empty", common.ContentTypeText)
 		return
 	}
 	key := context.ReadQueryParameter("key")
-	revisions, err := service.HistoryService.GetHistory(context.Ctx, labelID, service.WithKey(key))
+	revisions, err := service.HistoryService.GetHistory(context.Ctx, keyID,
+		service.WithKey(key),
+		service.WithLimit(limit),
+		service.WithOffset(offset))
 	if err != nil {
 		if err == service.ErrRevisionNotExist {
 			WriteErrResponse(context, http.StatusNotFound, err.Error(), common.ContentTypeText)
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index 02db656..784c408 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -96,10 +96,16 @@ func (r *KVResource) GetByKey(rctx *restful.Context) {
 		WriteErrResponse(rctx, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 		return
 	}
-	returnData(rctx, domain, project, labels, 0, 0)
+	limitStr := rctx.ReadQueryParameter("limit")
+	offsetStr := rctx.ReadQueryParameter("offset")
+	limit, offset, err := checkPagination(limitStr, offsetStr)
+	if err != nil {
+		WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), common.ContentTypeText)
+		return
+	}
+	returnData(rctx, domain, project, labels, limit, offset)
 }
 
-//List TODO pagination
 func (r *KVResource) List(rctx *restful.Context) {
 	var err error
 	project := rctx.ReadPathParameter("project")
@@ -113,8 +119,8 @@ func (r *KVResource) List(rctx *restful.Context) {
 		WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), common.ContentTypeText)
 		return
 	}
-	limitStr := rctx.ReadPathParameter("limit")
-	offsetStr := rctx.ReadPathParameter("offset")
+	limitStr := rctx.ReadQueryParameter("limit")
+	offsetStr := rctx.ReadQueryParameter("offset")
 	limit, offset, err := checkPagination(limitStr, offsetStr)
 	if err != nil {
 		WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), common.ContentTypeText)
@@ -123,12 +129,12 @@ func (r *KVResource) List(rctx *restful.Context) {
 	returnData(rctx, domain, project, labels, limit, offset)
 }
 
-func returnData(rctx *restful.Context, domain interface{}, project string, labels map[string]string, limit int64, offset int64) {
+func returnData(rctx *restful.Context, domain interface{}, project string, labels map[string]string, limit, offset int64) {
 	revStr := rctx.ReadQueryParameter(common.QueryParamRev)
 	wait := rctx.ReadQueryParameter(common.QueryParamWait)
 	if revStr == "" {
 		if wait == "" {
-			queryAndResponse(rctx, domain, project, "", labels, int(limit), int(offset))
+			queryAndResponse(rctx, domain, project, "", labels, limit, offset)
 			return
 		}
 		changed, err := eventHappened(rctx, wait, &pubsub.Topic{
@@ -141,7 +147,7 @@ func returnData(rctx *restful.Context, domain interface{}, project string, label
 			return
 		}
 		if changed {
-			queryAndResponse(rctx, domain, project, "", labels, int(limit), int(offset))
+			queryAndResponse(rctx, domain, project, "", labels, limit, offset)
 			return
 		}
 		rctx.WriteHeader(http.StatusNotModified)
@@ -156,7 +162,7 @@ func returnData(rctx *restful.Context, domain interface{}, project string, label
 			return
 		}
 		if revised {
-			queryAndResponse(rctx, domain, project, "", labels, int(limit), int(offset))
+			queryAndResponse(rctx, domain, project, "", labels, limit, offset)
 			return
 		} else if wait != "" {
 			changed, err := eventHappened(rctx, wait, &pubsub.Topic{
@@ -169,7 +175,7 @@ func returnData(rctx *restful.Context, domain interface{}, project string, label
 				return
 			}
 			if changed {
-				queryAndResponse(rctx, domain, project, "", labels, int(limit), int(offset))
+				queryAndResponse(rctx, domain, project, "", labels, limit, offset)
 				return
 			}
 			rctx.WriteHeader(http.StatusNotModified)
@@ -195,8 +201,17 @@ func (r *KVResource) Search(context *restful.Context) {
 		return
 	}
 	var kvs []*model.KVResponse
+	limitStr := context.ReadQueryParameter("limit")
+	offsetStr := context.ReadQueryParameter("offset")
+	limit, offset, err := checkPagination(limitStr, offsetStr)
+	if err != nil {
+		WriteErrResponse(context, http.StatusBadRequest, err.Error(), common.ContentTypeText)
+		return
+	}
 	if labelCombinations == nil {
-		result, err := service.KVService.FindKV(context.Ctx, domain.(string), project)
+		result, err := service.KVService.FindKV(context.Ctx, domain.(string), project,
+			service.WithLimit(limit),
+			service.WithOffset(offset))
 		if err != nil {
 			openlogging.Error("can not find by labels", openlogging.WithTags(openlogging.Tags{
 				"err": err.Error(),
@@ -210,7 +225,10 @@ func (r *KVResource) Search(context *restful.Context) {
 		openlogging.Debug("find by combination", openlogging.WithTags(openlogging.Tags{
 			"q": labels,
 		}))
-		result, err := service.KVService.FindKV(context.Ctx, domain.(string), project, service.WithLabels(labels))
+		result, err := service.KVService.FindKV(context.Ctx, domain.(string), project,
+			service.WithLabels(labels),
+			service.WithLimit(limit),
+			service.WithOffset(offset))
 		if err != nil {
 			if err == service.ErrKeyNotExists {
 				continue
diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go
index 4354a3a..a60bb30 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -56,7 +56,7 @@ func init() {
 		ListenPeerAddr: "127.0.0.1:4000",
 		AdvertiseAddr:  "127.0.0.1:4000",
 	}
-	config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017"
+	config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017/kie"
 	err := service.DBInit()
 	if err != nil {
 		panic(err)
@@ -220,6 +220,40 @@ func TestKVResource_List(t *testing.T) {
 		duration := time.Since(start)
 		t.Log(duration)
 	})
+	t.Run("list kv by service label offset, should return 1kv", func(t *testing.T) {
+		r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&offset=1", nil)
+		noopH := &handler2.NoopAuthHandler{}
+		chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
+		r.Header.Set("Content-Type", "application/json")
+		kvr := &v1.KVResource{}
+		c, err := restfultest.New(kvr, chain)
+		assert.NoError(t, err)
+		resp := httptest.NewRecorder()
+		c.ServeHTTP(resp, r)
+		body, err := ioutil.ReadAll(resp.Body)
+		assert.NoError(t, err)
+		result := &model.KVResponse{}
+		err = json.Unmarshal(body, result)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(result.Data))
+	})
+	t.Run("list kv by service label limit, should return 1kv", func(t *testing.T) {
+		r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&limit=1", nil)
+		noopH := &handler2.NoopAuthHandler{}
+		chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
+		r.Header.Set("Content-Type", "application/json")
+		kvr := &v1.KVResource{}
+		c, err := restfultest.New(kvr, chain)
+		assert.NoError(t, err)
+		resp := httptest.NewRecorder()
+		c.ServeHTTP(resp, r)
+		body, err := ioutil.ReadAll(resp.Body)
+		assert.NoError(t, err)
+		result := &model.KVResponse{}
+		err = json.Unmarshal(body, result)
+		assert.NoError(t, err)
+		assert.Equal(t, 1, len(result.Data))
+	})
 }
 func TestKVResource_GetByKey(t *testing.T) {
 	t.Run("get one key by label, exact match,should return 1 kv", func(t *testing.T) {
diff --git a/server/service/mongo/counter/revision_test.go b/server/service/mongo/counter/revision_test.go
index 744fd9f..d298942 100644
--- a/server/service/mongo/counter/revision_test.go
+++ b/server/service/mongo/counter/revision_test.go
@@ -28,7 +28,7 @@ import (
 
 func TestIncreaseAndGetRevision(t *testing.T) {
 	var err error
-	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017"}}
+	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017/kie"}}
 	err = session.Init()
 	assert.NoError(t, err)
 	s := &counter.Service{}
diff --git a/server/service/mongo/history/dao.go b/server/service/mongo/history/dao.go
index d9021c4..0057bbc 100644
--- a/server/service/mongo/history/dao.go
+++ b/server/service/mongo/history/dao.go
@@ -27,11 +27,18 @@ import (
 	"go.mongodb.org/mongo-driver/mongo/options"
 )
 
-func getHistoryByKeyID(ctx context.Context, filter bson.M) ([]*model.KVDoc, error) {
+func getHistoryByKeyID(ctx context.Context, filter bson.M, limit, offset int64) ([]*model.KVDoc, error) {
 	collection := session.GetDB().Collection(session.CollectionKVRevision)
-	cur, err := collection.Find(ctx, filter, options.Find().SetSort(map[string]interface{}{
+	opt := options.Find().SetSort(map[string]interface{}{
 		"revision": -1,
-	}))
+	})
+	if limit != 0 {
+		opt = opt.SetLimit(limit)
+	}
+	if offset != 0 {
+		opt = opt.SetSkip(offset)
+	}
+	cur, err := collection.Find(ctx, filter, opt)
 	if err != nil {
 		return nil, err
 	}
diff --git a/server/service/mongo/history/service.go b/server/service/mongo/history/service.go
index 19f5969..9502e75 100644
--- a/server/service/mongo/history/service.go
+++ b/server/service/mongo/history/service.go
@@ -40,5 +40,5 @@ func (s *Service) GetHistory(ctx context.Context, kvID string, options ...servic
 		"id": kvID,
 	}
 
-	return getHistoryByKeyID(ctx, filter)
+	return getHistoryByKeyID(ctx, filter, opts.Limit, opts.Offset)
 }
diff --git a/server/service/mongo/history/service_test.go b/server/service/mongo/history/service_test.go
index 7cb0cb3..ea0aa77 100644
--- a/server/service/mongo/history/service_test.go
+++ b/server/service/mongo/history/service_test.go
@@ -27,7 +27,7 @@ import (
 )
 
 func init() {
-	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017"}}
+	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017/kie"}}
 	_ = session.Init()
 }
 
diff --git a/server/service/mongo/kv/kv_dao.go b/server/service/mongo/kv/kv_dao.go
index 74ed8d1..15e8eaa 100644
--- a/server/service/mongo/kv/kv_dao.go
+++ b/server/service/mongo/kv/kv_dao.go
@@ -29,6 +29,7 @@ import (
 	uuid "github.com/satori/go.uuid"
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
 )
 
 //createKey get latest revision from history
@@ -111,7 +112,14 @@ func findKV(ctx context.Context, domain string, project string, opts service.Fin
 			filter["labels."+k] = v
 		}
 	}
-	cur, err := collection.Find(ctx, filter)
+	opt := options.Find()
+	if opts.Limit != 0 {
+		opt = opt.SetLimit(opts.Limit)
+	}
+	if opts.Offset != 0 {
+		opt = opt.SetSkip(opts.Offset)
+	}
+	cur, err := collection.Find(ctx, filter, opt)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
 			openlogging.Error("find kv failed, deadline exceeded", openlogging.WithTags(openlogging.Tags{
diff --git a/server/service/mongo/kv/kv_service.go b/server/service/mongo/kv/kv_service.go
index 96d35de..49bd7ef 100644
--- a/server/service/mongo/kv/kv_service.go
+++ b/server/service/mongo/kv/kv_service.go
@@ -31,6 +31,12 @@ import (
 	"github.com/go-mesh/openlogging"
 )
 
+//const
+const (
+	existKvLimit  = 2
+	existKvOffset = 0
+)
+
 //Service operate data in mongodb
 type Service struct {
 	timeout time.Duration
@@ -111,7 +117,11 @@ func (s *Service) Exist(ctx context.Context, domain, key string, project string,
 		return kvs[0], nil
 	}
 	kvs, err := s.FindKV(ctx, domain, project,
-		service.WithExactLabels(), service.WithLabels(opts.Labels), service.WithKey(key))
+		service.WithExactLabels(),
+		service.WithLabels(opts.Labels),
+		service.WithKey(key),
+		service.WithLimit(existKvLimit),
+		service.WithOffset(existKvOffset))
 	if err != nil {
 		openlogging.Error(err.Error())
 		return nil, err
@@ -147,7 +157,7 @@ func (s *Service) Delete(ctx context.Context, kvID string, domain string, projec
 }
 
 //List get kv list by key and criteria
-func (s *Service) List(ctx context.Context, domain, project string, limit, offset int, options ...service.FindOption) (*model.KVResponse, error) {
+func (s *Service) List(ctx context.Context, domain, project string, options ...service.FindOption) (*model.KVResponse, error) {
 	opts := service.NewDefaultFindOpts()
 	for _, o := range options {
 		o(&opts)
diff --git a/server/service/mongo/kv/kv_test.go b/server/service/mongo/kv/kv_test.go
index 710ad58..cd52ea7 100644
--- a/server/service/mongo/kv/kv_test.go
+++ b/server/service/mongo/kv/kv_test.go
@@ -30,7 +30,7 @@ import (
 
 func TestService_CreateOrUpdate(t *testing.T) {
 	var err error
-	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017"}}
+	config.Configurations = &config.Config{DB: config.DB{URI: "mongodb://kie:123@127.0.0.1:27017/kie"}}
 	err = session.Init()
 	assert.NoError(t, err)
 	kvsvc := &kv.Service{}
@@ -81,9 +81,12 @@ func TestService_CreateOrUpdate(t *testing.T) {
 			Project: "test",
 		})
 		assert.NoError(t, err)
-		kvs1, err := kvsvc.FindKV(context.Background(), "default", "test", service.WithKey("timeout"), service.WithLabels(map[string]string{
-			"app": "mall",
-		}), service.WithExactLabels())
+		kvs1, err := kvsvc.FindKV(context.Background(), "default", "test",
+			service.WithKey("timeout"),
+			service.WithLabels(map[string]string{
+				"app": "mall",
+			}),
+			service.WithExactLabels())
 		assert.Equal(t, beforeKV.Value, kvs1[0].Data[0].Value)
 		afterKV, err := kvsvc.CreateOrUpdate(context.Background(), &model.KVDoc{
 			Key:   "timeout",
@@ -99,9 +102,12 @@ func TestService_CreateOrUpdate(t *testing.T) {
 			"app": "mall",
 		}))
 		assert.Equal(t, beforeKV.ID, savedKV.ID)
-		kvs, err := kvsvc.FindKV(context.Background(), "default", "test", service.WithKey("timeout"), service.WithLabels(map[string]string{
-			"app": "mall",
-		}), service.WithExactLabels())
+		kvs, err := kvsvc.FindKV(context.Background(), "default", "test",
+			service.WithKey("timeout"),
+			service.WithLabels(map[string]string{
+				"app": "mall",
+			}),
+			service.WithExactLabels())
 		assert.Equal(t, afterKV.Value, kvs[0].Data[0].Value)
 	})
 
@@ -120,10 +126,11 @@ func TestService_FindKV(t *testing.T) {
 		assert.Equal(t, 1, len(kvs))
 	})
 	t.Run("greedy find by labels,with labels app ans service ", func(t *testing.T) {
-		kvs, err := kvsvc.FindKV(context.Background(), "default", "test", service.WithLabels(map[string]string{
-			"app":     "mall",
-			"service": "cart",
-		}))
+		kvs, err := kvsvc.FindKV(context.Background(), "default", "test",
+			service.WithLabels(map[string]string{
+				"app":     "mall",
+				"service": "cart",
+			}))
 		assert.NoError(t, err)
 		assert.Equal(t, 1, len(kvs))
 	})
diff --git a/server/service/options.go b/server/service/options.go
index 9fe41c6..bae00a7 100644
--- a/server/service/options.go
+++ b/server/service/options.go
@@ -38,6 +38,8 @@ type FindOptions struct {
 	LabelID     string
 	ClearLabel  bool
 	Timeout     time.Duration
+	Limit       int64
+	Offset      int64
 }
 
 //FindOption is functional option to find key value
@@ -91,3 +93,17 @@ func WithOutLabelField() FindOption {
 		o.ClearLabel = true
 	}
 }
+
+//WithLimit tells service paging limit
+func WithLimit(l int64) FindOption {
+	return func(o *FindOptions) {
+		o.Limit = l
+	}
+}
+
+//WithOffset tells service paging offset
+func WithOffset(os int64) FindOption {
+	return func(o *FindOptions) {
+		o.Offset = os
+	}
+}
diff --git a/server/service/service.go b/server/service/service.go
index e6e5310..b3a82c7 100644
--- a/server/service/service.go
+++ b/server/service/service.go
@@ -41,7 +41,7 @@ var (
 type KV interface {
 	//below 3 methods is usually for admin console
 	CreateOrUpdate(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error)
-	List(ctx context.Context, domain, project string, limit, offset int, options ...FindOption) (*model.KVResponse, error)
+	List(ctx context.Context, domain, project string, options ...FindOption) (*model.KVResponse, error)
 	Delete(ctx context.Context, kvID string, domain, project string) error
 	//FindKV is usually for service to pull configs
 	FindKV(ctx context.Context, domain, project string, options ...FindOption) ([]*model.KVResponse, error)