From b26157231d32a4e659e17bee63bb48d013e14056 Mon Sep 17 00:00:00 2001
From: Valera Shaitorov <shaitorov@perx.ru>
Date: Mon, 17 Apr 2023 15:55:25 +0700
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?=
 =?UTF-8?q?=D0=BD=D1=8B=20Collections=20middlewares?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../middleware/caching_middleware.go          | 136 ++++++
 .../middleware/caching_middleware_test.go     | 458 ++++++++++++++++++
 .../middleware/error_logging_middleware.go    | 101 ++++
 .../middleware/logging_middleware.go          | 296 +++++++++++
 pkg/collections/middleware/middleware.go      |  28 ++
 .../middleware/recovering_middleware.go       | 116 +++++
 6 files changed, 1135 insertions(+)
 create mode 100644 pkg/collections/middleware/caching_middleware.go
 create mode 100644 pkg/collections/middleware/caching_middleware_test.go
 create mode 100644 pkg/collections/middleware/error_logging_middleware.go
 create mode 100644 pkg/collections/middleware/logging_middleware.go
 create mode 100644 pkg/collections/middleware/middleware.go
 create mode 100644 pkg/collections/middleware/recovering_middleware.go

diff --git a/pkg/collections/middleware/caching_middleware.go b/pkg/collections/middleware/caching_middleware.go
new file mode 100644
index 00000000..0bb41c9e
--- /dev/null
+++ b/pkg/collections/middleware/caching_middleware.go
@@ -0,0 +1,136 @@
+package service
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	service "git.perx.ru/perxis/perxis-go/pkg/collections"
+	envService "git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+)
+
+func makeKey(spaceId, envId, collectionId string, disableSchemaIncludes bool) string {
+	s := spaceId + "-" + envId + "-" + collectionId + "-"
+	if disableSchemaIncludes {
+		s += "1"
+	} else {
+		s += "0"
+	}
+	return s
+}
+
+func CachingMiddleware(cache *cache.Cache, envs envService.Environments) Middleware {
+	return func(next service.Collections) service.Collections {
+		return &cachingMiddleware{
+			cache: cache,
+			next:  next,
+			envs:  envs,
+		}
+	}
+}
+
+type cachingMiddleware struct {
+	cache *cache.Cache
+	next  service.Collections
+	envs  envService.Environments
+}
+
+func (m cachingMiddleware) Create(ctx context.Context, collection *service.Collection) (coll *service.Collection, err error) {
+	return m.next.Create(ctx, collection)
+}
+
+func (m cachingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*service.GetOptions) (coll *service.Collection, err error) {
+
+	opts := service.MergeGetOptions(options...)
+	value, e := m.cache.Get(makeKey(spaceId, envId, collectionId, opts.DisableSchemaIncludes))
+	if e == nil {
+		return value.(*service.Collection), err
+	}
+	coll, err = m.next.Get(ctx, spaceId, envId, collectionId, options...)
+	if err == nil {
+		env, err := m.envs.Get(ctx, coll.SpaceID, coll.EnvID)
+		if err != nil {
+			return nil, err
+		}
+		m.cache.Set(makeKey(coll.SpaceID, env.ID, coll.ID, opts.DisableSchemaIncludes), coll)
+		for _, al := range env.Aliases {
+			m.cache.Set(makeKey(coll.SpaceID, al, coll.ID, opts.DisableSchemaIncludes), coll)
+		}
+
+	}
+	return coll, err
+}
+
+func (m cachingMiddleware) List(ctx context.Context, spaceId, envId string, filter *service.Filter) (collections []*service.Collection, err error) {
+	return m.next.List(ctx, spaceId, envId, filter)
+}
+
+func (m cachingMiddleware) Update(ctx context.Context, coll *service.Collection) (err error) {
+
+	err = m.next.Update(ctx, coll)
+	if err == nil {
+		env, err := m.envs.Get(ctx, coll.SpaceID, coll.EnvID)
+		if err != nil {
+			return err
+		}
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, coll.ID, true))
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, coll.ID, false))
+		for _, al := range env.Aliases {
+			m.cache.Remove(makeKey(env.SpaceID, al, coll.ID, true))
+			m.cache.Remove(makeKey(env.SpaceID, al, coll.ID, false))
+		}
+	}
+	return err
+}
+
+func (m cachingMiddleware) SetSchema(ctx context.Context, spaceId, envId, collectionId string, schema *schema.Schema) (err error) {
+	err = m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
+	if err == nil {
+		env, err := m.envs.Get(ctx, spaceId, envId)
+		if err != nil {
+			return err
+		}
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, true))
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, false))
+		for _, al := range env.Aliases {
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, true))
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, false))
+		}
+	}
+	return err
+}
+
+func (m cachingMiddleware) SetState(ctx context.Context, spaceId, envId, collectionId string, state *service.StateInfo) (err error) {
+	err = m.next.SetState(ctx, spaceId, envId, collectionId, state)
+	if err == nil {
+		env, err := m.envs.Get(ctx, spaceId, envId)
+		if err != nil {
+			return err
+		}
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, true))
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, false))
+		for _, al := range env.Aliases {
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, true))
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, false))
+		}
+	}
+	return err
+}
+
+func (m cachingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
+
+	err = m.next.Delete(ctx, spaceId, envId, collectionId)
+	if err == nil {
+		env, err := m.envs.Get(ctx, spaceId, envId)
+		if err != nil {
+			return err
+		}
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, true))
+		m.cache.Remove(makeKey(env.SpaceID, env.ID, collectionId, false))
+		for _, al := range env.Aliases {
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, true))
+			m.cache.Remove(makeKey(env.SpaceID, al, collectionId, false))
+		}
+	}
+	return err
+}
diff --git a/pkg/collections/middleware/caching_middleware_test.go b/pkg/collections/middleware/caching_middleware_test.go
new file mode 100644
index 00000000..3a5f88e6
--- /dev/null
+++ b/pkg/collections/middleware/caching_middleware_test.go
@@ -0,0 +1,458 @@
+package service
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	colsmocks "git.perx.ru/perxis/perxis-go/pkg/collections/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	envmocks "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCollections_Cache(t *testing.T) {
+
+	const (
+		colID    = "colID"
+		spaceID  = "spaceID"
+		envID    = "envId"
+		envAlias = "envAlias"
+		size     = 5
+		ttl      = 20 * time.Millisecond
+	)
+
+	ErrNotFound := errors.NotFound(errors.New("not found"))
+
+	ctx := context.Background()
+
+	t.Run("Get from cache", func(t *testing.T) {
+		col := &colsmocks.Collections{}
+		env := &envmocks.Environments{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+		env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+		col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+
+		v1, err := svc.Get(ctx, spaceID, envID, colID)
+		require.NoError(t, err)
+
+		v2, err := svc.Get(ctx, spaceID, envID, colID)
+		require.NoError(t, err)
+		assert.Same(t, v1, v2, "Ожидается получение объекта из кеша при повторном запросе по ID окружения.")
+
+		v3, err := svc.Get(ctx, spaceID, envAlias, colID)
+		require.NoError(t, err)
+		assert.Same(t, v3, v2, "Ожидается получение объекта из кеша, при запросе того же объекта по alias окружения.")
+
+		env.AssertExpectations(t)
+		col.AssertExpectations(t)
+	})
+
+	t.Run("Get from cache(by Alias)", func(t *testing.T) {
+		col := &colsmocks.Collections{}
+		env := &envmocks.Environments{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+		col.On("Get", mock.Anything, spaceID, envAlias, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+		env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+
+		v1, err := svc.Get(ctx, spaceID, envAlias, colID)
+		require.NoError(t, err)
+
+		v2, err := svc.Get(ctx, spaceID, envAlias, colID)
+		require.NoError(t, err)
+		assert.Same(t, v1, v2, "Ожидается получение объекта из кеша при повторном запросе по Alias окружения.")
+
+		v3, err := svc.Get(ctx, spaceID, envID, colID)
+		require.NoError(t, err)
+		assert.Same(t, v3, v2, "Ожидается получение объекта из кеша, при запросе того же объекта по ID окружения.")
+
+		env.AssertExpectations(t)
+		col.AssertExpectations(t)
+	})
+
+	t.Run("Get from cache with options", func(t *testing.T) {
+		col := &colsmocks.Collections{}
+		env := &envmocks.Environments{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+		env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+		col.On("Get", mock.Anything, spaceID, envID, colID, mock.Anything).Run(func(args mock.Arguments) {
+			require.Len(t, args, 5)
+			opt := args.Get(4).(*collections.GetOptions)
+			assert.True(t, opt.DisableSchemaIncludes)
+		}).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+
+		_, err := svc.Get(ctx, spaceID, envID, colID, []*collections.GetOptions{{DisableSchemaIncludes: true}}...)
+		require.NoError(t, err)
+
+		env.AssertExpectations(t)
+		col.AssertExpectations(t)
+	})
+
+	//t.Run("List from cache", func(t *testing.T) {
+	//	col := &colsmocks.Collections{}
+	//	env := &envmocks.Environments{}
+	//
+	//	svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+	//
+	//	col.On("List", mock.Anything, spaceID, envID).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+	//	env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+	//
+	//	vl1, err := svc.List(ctx, spaceID, envID, nil)
+	//	require.NoError(t, err)
+	//
+	//	vl2, err := svc.List(ctx, spaceID, envID, nil)
+	//	require.NoError(t, err)
+	//	assert.Len(t, vl2, 1)
+	//	assert.Same(t, vl1[0], vl2[0], "При повторном запросе по ID окружения, ожидается получение списка объектов из кеша.")
+	//
+	//	vl3, err := svc.List(ctx, spaceID, envAlias, nil)
+	//	require.NoError(t, err)
+	//	assert.Len(t, vl3, 1)
+	//	assert.Same(t, vl3[0], vl2[0], "При повторном запросе по Alias окружения, ожидается получение списка объектов из кеша.")
+	//
+	//	env.AssertExpectations(t)
+	//	col.AssertExpectations(t)
+	//})
+
+	t.Run("List", func(t *testing.T) {
+		col := &colsmocks.Collections{}
+		env := &envmocks.Environments{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+		col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+		col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+		_, err := svc.List(ctx, spaceID, envAlias, nil)
+		require.NoError(t, err)
+
+		_, err = svc.List(ctx, spaceID, envID, nil)
+		require.NoError(t, err)
+
+		env.AssertExpectations(t)
+		col.AssertExpectations(t)
+	})
+
+	t.Run("Invalidate cache", func(t *testing.T) {
+		t.Run("After Update", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается получение объекта из кеша по ID окружения.")
+
+			v3, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v2, v3, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl1, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Update", mock.Anything, mock.Anything).Return(nil).Once()
+			err = svc.Update(ctx, &collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"})
+			require.NoError(t, err)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}}, nil).Once()
+
+			v4, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.NotSame(t, v3, v4, "Ожидает что элемент после обновления был удален из кэша и будет запрошен заново из сервиса.")
+
+			v5, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v4, v5, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl2, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.NotSame(t, vl1[0], vl2[0], "Ожидает что после обновления элементы будут запрошены заново из сервиса.")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+
+		t.Run("After Update(by Alias)", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			//env.On("Get", mock.Anything, spaceID, envAlias).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+
+			col.On("Get", mock.Anything, spaceID, envAlias, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			v3, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v2, v3, "Ожидается получение объекта из кеша по ID окружения.")
+
+			vl1, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Update", mock.Anything, mock.Anything).Return(nil).Once()
+			err = svc.Update(ctx, &collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"})
+			require.NoError(t, err)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			//env.On("Get", mock.Anything, spaceID, envAlias).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+
+			col.On("Get", mock.Anything, spaceID, envAlias, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}}, nil).Once()
+
+			v4, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.NotSame(t, v3, v4, "Ожидает что элемент после обновления был удален из кэша и будет запрошен заново из сервиса.")
+
+			v5, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v4, v5, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl4, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.NotSame(t, vl1[0], vl4[0], "Ожидает что после обновления элементы будут запрошены заново из сервиса.")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+
+		t.Run("After Set Schema", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Twice()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Twice()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается получение объекта из кеша по ID окружения.")
+
+			v3, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v2, v3, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl1, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается получение объектов из кеша по ID окружения.")
+
+			vl3, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Equal(t, vl2[0], vl3[0], "Ожидается получение объектов из кеша по Alias окружения.")
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("SetSchema", mock.Anything, spaceID, envID, colID, mock.Anything).Return(nil).Once()
+			err = svc.SetSchema(ctx, spaceID, envID, colID, &schema.Schema{})
+			require.NoError(t, err)
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "nameUPD"}}, nil).Once()
+
+			v4, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.NotSame(t, v3, v4, "Ожидает что элемент после обновления схемы был удален из кэша и будет запрошен заново из сервиса.")
+
+			v5, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v4, v5, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl4, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.NotSame(t, vl4[0], vl3[0], "Ожидает что после обновления схемы элементы будут запрошены заново из сервиса.")
+
+			vl5, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.Equal(t, vl4[0], vl5[0], "Ожидается получение объектов из кеша по Alias окружения..")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+
+		t.Run("After Delete", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Twice()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Twice()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается получение объекта из кеша по ID окружения.")
+
+			v3, err := svc.Get(ctx, spaceID, envAlias, colID)
+			require.NoError(t, err)
+			assert.Same(t, v2, v3, "Ожидается получение объекта из кеша по Alias окружения.")
+
+			vl1, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается получение объектов из кеша по ID окружения.")
+
+			vl3, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Equal(t, vl2[0], vl3[0], "Ожидается получение объектов из кеша по Alias окружения.")
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Delete", mock.Anything, spaceID, envID, colID).Return(nil).Once()
+			err = svc.Delete(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(nil, ErrNotFound).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{}, nil).Once()
+
+			_, err = svc.Get(ctx, spaceID, envID, colID)
+			require.Error(t, err)
+			assert.EqualError(t, err, "not found", "Ожидает что элемент был удален из кэша и получена ошибка от сервиса.")
+
+			vl4, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl4, 0, "Ожидает что элементы были удалены из кэша.")
+
+			col.On("Get", mock.Anything, spaceID, envAlias, colID).Return(nil, ErrNotFound).Once()
+
+			_, err = svc.Get(ctx, spaceID, envAlias, colID)
+			require.Error(t, err)
+			assert.EqualError(t, err, "not found", "Ожидает что элемент был удален из кэша и получена ошибка от сервиса.")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+
+		t.Run("After Create", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Twice()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}}, nil).Once()
+
+			vl1, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается получение объектов из кеша по ID окружения.")
+
+			vl3, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl2, 1)
+			assert.Equal(t, vl2[0], vl3[0], "Ожидается получение объектов из кеша по Alias окружения.")
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("Create", mock.Anything, mock.Anything).Return(&collections.Collection{ID: "colID2", SpaceID: spaceID, EnvID: envID, Name: "name2"}, nil).Once()
+			_, err = svc.Create(ctx, &collections.Collection{ID: "colID2", SpaceID: spaceID, EnvID: envID, Name: "name2"})
+			require.NoError(t, err)
+
+			//env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envID, mock.Anything).Return([]*collections.Collection{
+				{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"},
+				{ID: "colID2", SpaceID: spaceID, EnvID: envID, Name: "name2"},
+			}, nil).Once()
+			col.On("List", mock.Anything, spaceID, envAlias, mock.Anything).Return([]*collections.Collection{
+				{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"},
+				{ID: "colID2", SpaceID: spaceID, EnvID: envID, Name: "name2"},
+			}, nil).Once()
+
+			vl4, err := svc.List(ctx, spaceID, envID, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl4, 2, "Ожидает что элементы были удалены из кэша и получены заново из сервиса.")
+
+			vl5, err := svc.List(ctx, spaceID, envAlias, nil)
+			require.NoError(t, err)
+			assert.Len(t, vl5, 2)
+			assert.Equal(t, vl4[0], vl5[0], "Ожидается получение объектов из кеша по Alias окружения..")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+
+		t.Run("After TTL expired", func(t *testing.T) {
+			col := &colsmocks.Collections{}
+			env := &envmocks.Environments{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl), env)(col)
+
+			env.On("Get", mock.Anything, spaceID, envID).Return(&environments.Environment{ID: envID, SpaceID: spaceID, Aliases: []string{envAlias}}, nil)
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+
+			v1, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.Same(t, v1, v2, "Ожидается получение объекта из кеша.")
+
+			time.Sleep(2 * ttl)
+
+			col.On("Get", mock.Anything, spaceID, envID, colID).Return(&collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "name"}, nil).Once()
+
+			v3, err := svc.Get(ctx, spaceID, envID, colID)
+			require.NoError(t, err)
+			assert.NotSame(t, v3, v2, "Ожидает что элемент был удален из кэша и будет запрошен заново из сервиса.")
+
+			env.AssertExpectations(t)
+			col.AssertExpectations(t)
+		})
+	})
+
+}
diff --git a/pkg/collections/middleware/error_logging_middleware.go b/pkg/collections/middleware/error_logging_middleware.go
new file mode 100644
index 00000000..cdee51d6
--- /dev/null
+++ b/pkg/collections/middleware/error_logging_middleware.go
@@ -0,0 +1,101 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements collections.Collections that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   collections.Collections
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the collections.Collections with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next collections.Collections) collections.Collections {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Create(ctx, collection)
+}
+
+func (m *errorLoggingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Delete(ctx, spaceId, envId, collectionId)
+}
+
+func (m *errorLoggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Get(ctx, spaceId, envId, collectionId, options...)
+}
+
+func (m *errorLoggingMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.List(ctx, spaceId, envId, filter)
+}
+
+func (m *errorLoggingMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
+}
+
+func (m *errorLoggingMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.SetState(ctx, spaceId, envId, collectionId, state)
+}
+
+func (m *errorLoggingMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Update(ctx, coll)
+}
diff --git a/pkg/collections/middleware/logging_middleware.go b/pkg/collections/middleware/logging_middleware.go
new file mode 100644
index 00000000..53fcc846
--- /dev/null
+++ b/pkg/collections/middleware/logging_middleware.go
@@ -0,0 +1,296 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/access_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// loggingMiddleware implements collections.Collections that is instrumented with logging
+type loggingMiddleware struct {
+	logger *zap.Logger
+	next   collections.Collections
+}
+
+// LoggingMiddleware instruments an implementation of the collections.Collections with simple logging
+func LoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next collections.Collections) collections.Collections {
+		return &loggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *loggingMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":        ctx,
+		"collection": collection} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Create.Request", fields...)
+
+	created, err = m.next.Create(ctx, collection)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"created": created,
+		"err":     err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Create.Response", fields...)
+
+	return created, err
+}
+
+func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":          ctx,
+		"spaceId":      spaceId,
+		"envId":        envId,
+		"collectionId": collectionId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Request", fields...)
+
+	err = m.next.Delete(ctx, spaceId, envId, collectionId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":          ctx,
+		"spaceId":      spaceId,
+		"envId":        envId,
+		"collectionId": collectionId,
+		"options":      options} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Get.Request", fields...)
+
+	collection, err = m.next.Get(ctx, spaceId, envId, collectionId, options...)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"collection": collection,
+		"err":        err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Get.Response", fields...)
+
+	return collection, err
+}
+
+func (m *loggingMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":     ctx,
+		"spaceId": spaceId,
+		"envId":   envId,
+		"filter":  filter} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("List.Request", fields...)
+
+	collections, err = m.next.List(ctx, spaceId, envId, filter)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"collections": collections,
+		"err":         err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("List.Response", fields...)
+
+	return collections, err
+}
+
+func (m *loggingMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":          ctx,
+		"spaceId":      spaceId,
+		"envId":        envId,
+		"collectionId": collectionId,
+		"schema":       schema} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("SetSchema.Request", fields...)
+
+	err = m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("SetSchema.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":          ctx,
+		"spaceId":      spaceId,
+		"envId":        envId,
+		"collectionId": collectionId,
+		"state":        state} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("SetState.Request", fields...)
+
+	err = m.next.SetState(ctx, spaceId, envId, collectionId, state)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("SetState.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":  ctx,
+		"coll": coll} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Update.Request", fields...)
+
+	err = m.next.Update(ctx, coll)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Update.Response", fields...)
+
+	return err
+}
diff --git a/pkg/collections/middleware/middleware.go b/pkg/collections/middleware/middleware.go
new file mode 100644
index 00000000..911368f0
--- /dev/null
+++ b/pkg/collections/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/middleware
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"go.uber.org/zap"
+)
+
+type Middleware func(collections.Collections) collections.Collections
+
+func WithLog(s collections.Collections, logger *zap.Logger, log_access bool) collections.Collections {
+	if logger == nil {
+		logger = zap.NewNop()
+	}
+
+	logger = logger.Named("Collections")
+	s = ErrorLoggingMiddleware(logger)(s)
+	if log_access {
+		s = LoggingMiddleware(logger)(s)
+	}
+	s = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/pkg/collections/middleware/recovering_middleware.go b/pkg/collections/middleware/recovering_middleware.go
new file mode 100644
index 00000000..fb61326f
--- /dev/null
+++ b/pkg/collections/middleware/recovering_middleware.go
@@ -0,0 +1,116 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements collections.Collections that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   collections.Collections
+}
+
+// RecoveringMiddleware instruments an implementation of the collections.Collections with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next collections.Collections) collections.Collections {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Create(ctx context.Context, collection *collections.Collection) (created *collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Create(ctx, collection)
+}
+
+func (m *recoveringMiddleware) Delete(ctx context.Context, spaceId string, envId string, collectionId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Delete(ctx, spaceId, envId, collectionId)
+}
+
+func (m *recoveringMiddleware) Get(ctx context.Context, spaceId string, envId string, collectionId string, options ...*collections.GetOptions) (collection *collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Get(ctx, spaceId, envId, collectionId, options...)
+}
+
+func (m *recoveringMiddleware) List(ctx context.Context, spaceId string, envId string, filter *collections.Filter) (collections []*collections.Collection, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.List(ctx, spaceId, envId, filter)
+}
+
+func (m *recoveringMiddleware) SetSchema(ctx context.Context, spaceId string, envId string, collectionId string, schema *schema.Schema) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.SetSchema(ctx, spaceId, envId, collectionId, schema)
+}
+
+func (m *recoveringMiddleware) SetState(ctx context.Context, spaceId string, envId string, collectionId string, state *collections.StateInfo) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.SetState(ctx, spaceId, envId, collectionId, state)
+}
+
+func (m *recoveringMiddleware) Update(ctx context.Context, coll *collections.Collection) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Update(ctx, coll)
+}
-- 
GitLab