diff --git a/go.mod b/go.mod
index 4a82fbec0e49aa45415ca88d6e6331d477fdd44b..f53323bb290e119feda2d24ebe935d02cfdc9e38 100644
--- a/go.mod
+++ b/go.mod
@@ -8,10 +8,12 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/gosimple/slug v1.13.1
 	github.com/hashicorp/go-multierror v1.1.1
+	github.com/hashicorp/golang-lru v0.5.4
 	github.com/pkg/errors v0.9.1
 	github.com/rs/xid v1.4.0
 	github.com/stretchr/testify v1.8.0
 	go.mongodb.org/mongo-driver v1.11.4
+	go.uber.org/zap v1.19.1
 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
 	google.golang.org/grpc v1.45.0
@@ -28,6 +30,8 @@ require (
 	github.com/hashicorp/errwrap v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/stretchr/objx v0.4.0 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	go.uber.org/multierr v1.7.0 // indirect
 	golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
 	golang.org/x/text v0.3.7 // indirect
 	google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect
diff --git a/go.sum b/go.sum
index 1f26de451e3ef4f777baaefada69e5af8b447921..9dea84ab8c222f13374d0a59a405fa290feed61a 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
 github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -75,6 +77,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -91,6 +95,7 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -108,6 +113,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -124,6 +130,16 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
 go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -133,6 +149,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -228,7 +245,10 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f7248e5208f1691d7e094eaf640f361383faab6
--- /dev/null
+++ b/pkg/cache/cache.go
@@ -0,0 +1,90 @@
+package cache
+
+import (
+	"errors"
+	"fmt"
+	"time"
+
+	lru "github.com/hashicorp/golang-lru"
+	"go.uber.org/zap"
+)
+
+const (
+	defaultCacheSize = 1000
+	defaultTTL       = 30 * time.Second
+)
+
+var ErrNotFound = errors.New("not found")
+
+type Cache struct {
+	cache  *lru.Cache
+	ttl    time.Duration
+	logger *zap.Logger
+}
+
+type item struct {
+	value     interface{}
+	expiredAt time.Time
+}
+
+func NewCache(size int, ttl time.Duration, opts ...interface{}) *Cache {
+	if size == 0 {
+		size = defaultCacheSize
+	}
+	if ttl == 0 {
+		ttl = defaultTTL
+	}
+	c, err := lru.New(size)
+	if err != nil {
+		panic(err)
+	}
+	ch := &Cache{
+		cache:  c,
+		ttl:    ttl,
+		logger: zap.NewNop(),
+	}
+
+	for _, o := range opts {
+		switch p := o.(type) {
+		case *zap.Logger:
+			ch.logger = p
+		}
+	}
+
+	ch.logger = ch.logger.Named("Cache")
+
+	return ch
+}
+
+func (c *Cache) Set(key, value interface{}) (err error) {
+	c.cache.Add(key, &item{value: value, expiredAt: time.Now().Add(c.ttl)})
+	c.logger.Debug("Set", zap.String("key", fmt.Sprintf("%v", key)), zap.String("ptr", fmt.Sprintf("%p", value)))
+	return nil
+}
+
+func (c *Cache) Get(key interface{}) (value interface{}, err error) {
+	val, ok := c.cache.Get(key)
+	if ok {
+		v := val.(*item)
+		if v.expiredAt.Before(time.Now()) {
+			c.Remove(key)
+			c.logger.Debug("Expired", zap.String("key", fmt.Sprintf("%v", key)), zap.String("ptr", fmt.Sprintf("%p", v.value)))
+			return nil, ErrNotFound
+		}
+		c.logger.Debug("Hit", zap.String("key", fmt.Sprintf("%v", key)), zap.String("ptr", fmt.Sprintf("%p", v.value)))
+		return v.value, nil
+	}
+	c.logger.Debug("Miss", zap.String("key", fmt.Sprintf("%v", key)))
+	return nil, ErrNotFound
+}
+
+func (c *Cache) Remove(key interface{}) (err error) {
+	present := c.cache.Remove(key)
+	c.logger.Debug("Remove", zap.String("key", fmt.Sprintf("%v", key)))
+
+	if !present {
+		err = ErrNotFound
+	}
+
+	return
+}
diff --git a/pkg/cache/cache_test.go b/pkg/cache/cache_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..345a391c09044b61ebac4f869ba26bbcd9def13d
--- /dev/null
+++ b/pkg/cache/cache_test.go
@@ -0,0 +1,82 @@
+package cache
+
+import (
+	"errors"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCache(t *testing.T) {
+
+	t.Run("Simple", func(t *testing.T) {
+		c := NewCache(10, 0)
+
+		{
+			val, err := c.Get("test_key")
+			require.Error(t, err)
+			assert.True(t, errors.Is(err, ErrNotFound))
+			assert.Nil(t, val)
+		}
+		{
+			err := c.Set("test_key", "test_val")
+			require.NoError(t, err)
+
+			val, err := c.Get("test_key")
+			require.NoError(t, err)
+			assert.Equal(t, "test_val", val.(string))
+		}
+
+		{
+			err := c.Remove("test_key")
+			require.NoError(t, err)
+
+			val, err := c.Get("test_key")
+			assert.True(t, errors.Is(err, ErrNotFound))
+			assert.Nil(t, val)
+		}
+	})
+	t.Run("Value Evicted", func(t *testing.T) {
+		c := NewCache(1, 0)
+
+		{
+			err := c.Set("test_key_1", "test_val_1")
+			require.NoError(t, err)
+
+			val, err := c.Get("test_key_1")
+			require.NoError(t, err)
+			assert.Equal(t, "test_val_1", val.(string))
+		}
+
+		{
+			err := c.Set("test_key_2", "test_val_2")
+			require.NoError(t, err)
+
+			val, err := c.Get("test_key_1")
+			assert.True(t, errors.Is(err, ErrNotFound))
+			assert.Nil(t, val)
+			val, err = c.Get("test_key_2")
+			require.NoError(t, err)
+			assert.Equal(t, "test_val_2", val.(string))
+		}
+
+	})
+	t.Run("TTL expired", func(t *testing.T) {
+		c := NewCache(10, 10*time.Millisecond)
+
+		err := c.Set("test_key", "test_val")
+		require.NoError(t, err)
+
+		val, err := c.Get("test_key")
+		require.NoError(t, err)
+		assert.Equal(t, "test_val", val.(string))
+
+		time.Sleep(15 * time.Millisecond)
+
+		val, err = c.Get("test_key")
+		assert.True(t, errors.Is(err, ErrNotFound))
+		assert.Nil(t, val)
+	})
+}
diff --git a/pkg/clients/client.go b/pkg/clients/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..f38b5acc9b442be39139523bcd316947d5d54fe7
--- /dev/null
+++ b/pkg/clients/client.go
@@ -0,0 +1,87 @@
+package clients
+
+// Client - приложение имеющее доступ к API
+type Client struct {
+	// Внутренний идентификатор клиента внутри системы
+	ID string `json:"id" bson:"_id"`
+
+	// Идентификатор пространства
+	SpaceID string `json:"space_id" bson:"-"`
+
+	// Имя приложения (обязательное поле)
+	Name string `json:"name" bson:"name"`
+
+	// Параметры аутентификации клиента
+	OAuth  *OAuth  `json:"oauth,omitempty" bson:"oauth,omitempty"`
+	TLS    *TLS    `json:"tls,omitempty" bson:"tls,omitempty"`
+	APIKey *APIKey `json:"api_key,omitempty" bson:"api_key,omitempty"`
+
+	// Описание клиента, назначение
+	Description string `json:"description" bson:"description"`
+
+	// Приложение отключено и не может авторизоваться
+	Disabled *bool `json:"disabled,omitempty" bson:"disabled,omitempty"`
+
+	// Роль приложения в пространстве
+	RoleID string `json:"role_id" bson:"role_id"`
+}
+
+type OAuth struct {
+	ClientID     string `bson:"client_id,omitempty" json:"client_id,omitempty"`         // Идентификатор клиента выданные IdP сервером, используется для идентификации клиента
+	AuthID       string `bson:"auth_id,omitempty" json:"auth_id,omitempty"`             // Сервис, который используется для авторизации клиента
+	TokenURL     string `bson:"token_url,omitempty" json:"token_url,omitempty"`         // URL для получения/обновления access token клиента (опционально)
+	ClientSecret string `bson:"client_secret,omitempty" json:"client_secret,omitempty"` // Секретный Ключ клиента, используется для идентификации клиента (опционально)
+}
+
+type APIKey struct {
+	Key    string `bson:"key,omitempty" json:"key,omitempty"`
+	Rotate bool   `bson:"-" json:"rotate,omitempty"`
+}
+
+type TLS struct {
+	Subject string `json:"subject,omitempty"`
+	CACert  string `json:"ca_cert,omitempty"`
+	Cert    string `json:"cert,omitempty"`
+	Key     string `json:"key,omitempty"`
+}
+
+func (c *Client) SetDisabled(b bool) *Client {
+	c.Disabled = &b
+	return c
+}
+
+func (c *Client) IsDisabled() bool {
+	if c.Disabled != nil && *c.Disabled {
+		return true
+	}
+	return false
+}
+
+func (c Client) Clone() *Client {
+	clone := &Client{
+		ID:          c.ID,
+		SpaceID:     c.SpaceID,
+		Name:        c.Name,
+		Description: c.Description,
+		RoleID:      c.RoleID,
+	}
+
+	if c.OAuth != nil {
+		temp := *c.OAuth
+		clone.OAuth = &temp
+	}
+	if c.TLS != nil {
+		temp := *c.TLS
+		clone.TLS = &temp
+	}
+	if c.APIKey != nil {
+		temp := *c.APIKey
+		clone.APIKey = &temp
+	}
+	if c.Disabled != nil {
+		temp := *c.Disabled
+		clone.Disabled = &temp
+	}
+
+	return clone
+}
diff --git a/pkg/clients/mocks/Clients.go b/pkg/clients/mocks/Clients.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfeb7e946a1fe50d044479ba785aac15f484ac31
--- /dev/null
+++ b/pkg/clients/mocks/Clients.go
@@ -0,0 +1,149 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Clients is an autogenerated mock type for the Clients type
+type Clients struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, client
+func (_m *Clients) Create(ctx context.Context, client *clients.Client) (*clients.Client, error) {
+	ret := _m.Called(ctx, client)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, *clients.Client) *clients.Client); ok {
+		r0 = rf(ctx, client)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *clients.Client) error); ok {
+		r1 = rf(ctx, client)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, id
+func (_m *Clients) Delete(ctx context.Context, spaceId string, id string) error {
+	ret := _m.Called(ctx, spaceId, id)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, id)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Enable provides a mock function with given fields: ctx, spaceId, id, enable
+func (_m *Clients) Enable(ctx context.Context, spaceId string, id string, enable bool) error {
+	ret := _m.Called(ctx, spaceId, id, enable)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) error); ok {
+		r0 = rf(ctx, spaceId, id, enable)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, id
+func (_m *Clients) Get(ctx context.Context, spaceId string, id string) (*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId, id)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *clients.Client); ok {
+		r0 = rf(ctx, spaceId, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetBy provides a mock function with given fields: ctx, spaceId, params
+func (_m *Clients) GetBy(ctx context.Context, spaceId string, params *clients.GetByParams) (*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId, params)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string, *clients.GetByParams) *clients.Client); ok {
+		r0 = rf(ctx, spaceId, params)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, *clients.GetByParams) error); ok {
+		r1 = rf(ctx, spaceId, params)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Clients) List(ctx context.Context, spaceId string) ([]*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*clients.Client); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: ctx, client
+func (_m *Clients) Update(ctx context.Context, client *clients.Client) error {
+	ret := _m.Called(ctx, client)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *clients.Client) error); ok {
+		r0 = rf(ctx, client)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/clients/service.go b/pkg/clients/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..823de82bdbe720e2a9ff24f069cb94458bf1cf25
--- /dev/null
+++ b/pkg/clients/service.go
@@ -0,0 +1,37 @@
+package clients
+
+import (
+	"context"
+)
+
+type GetByParams struct {
+	OAuthClientID string `json:"oauth_client_id,omitempty"`
+	APIKey        string `json:"api_key,omitempty"`
+	TLSSubject    string `json:"tls_subject,omitempty"`
+}
+
+// @microgen grpc, recovering, middleware
+// @protobuf git.perx.ru/perxis/perxis-go/proto/clients
+// @grpc-addr content.clients.Clients
+type Clients interface {
+	// Create - создает клиента (приложение) для работы с API
+	Create(ctx context.Context, client *Client) (created *Client, err error)
+
+	// Get - возвращает клиента по id
+	Get(ctx context.Context, spaceId, id string) (client *Client, err error)
+
+	// GetBy - возвращает клиента по идентификатору системы авторизации
+	GetBy(ctx context.Context, spaceId string, params *GetByParams) (client *Client, err error)
+
+	// List - возвращает список клиентов созданных в пространстве
+	List(ctx context.Context, spaceId string) (clients []*Client, err error)
+
+	// Update - обновляет параметры клиента
+	Update(ctx context.Context, client *Client) (err error)
+
+	// Delete - удаляет указанного клиента из пространстве
+	Delete(ctx context.Context, spaceId, id string) (err error)
+
+	// Enable - активирует/деактивирует клиента. Клиент не сможет обращаться к API платформы
+	Enable(ctx context.Context, spaceId, id string, enable bool) (err error)
+}
diff --git a/pkg/clients/transport/client.microgen.go b/pkg/clients/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..633855af4d1f7fe74260ec7b4ece23db6063ebfb
--- /dev/null
+++ b/pkg/clients/transport/client.microgen.go
@@ -0,0 +1,108 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *clients.Client) (res0 *clients.Client, res1 error) {
+	request := CreateRequest{Client: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string, arg2 string) (res0 *clients.Client, res1 error) {
+	request := GetRequest{
+		Id:      arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Client, res1
+}
+
+func (set EndpointsSet) GetBy(arg0 context.Context, arg1 string, arg2 *clients.GetByParams) (res0 *clients.Client, res1 error) {
+	request := GetByRequest{
+		Config:  arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetByEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetByResponse).Client, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*clients.Client, res1 error) {
+	request := ListRequest{SpaceId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListResponse).Clients, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *clients.Client) (res0 error) {
+	request := UpdateRequest{Client: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		Id:      arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Enable(arg0 context.Context, arg1 string, arg2 string, arg3 bool) (res0 error) {
+	request := EnableRequest{
+		Enable:  arg3,
+		Id:      arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.EnableEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/clients/transport/endpoints.microgen.go b/pkg/clients/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..bf73c784e68eb95f1dc631184046a4016bcca7a1
--- /dev/null
+++ b/pkg/clients/transport/endpoints.microgen.go
@@ -0,0 +1,16 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Clients API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	GetEndpoint    endpoint.Endpoint
+	GetByEndpoint  endpoint.Endpoint
+	ListEndpoint   endpoint.Endpoint
+	UpdateEndpoint endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+	EnableEndpoint endpoint.Endpoint
+}
diff --git a/pkg/clients/transport/exchanges.microgen.go b/pkg/clients/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a1a8e65967661b117a20ff62fc6e20837afce2b
--- /dev/null
+++ b/pkg/clients/transport/exchanges.microgen.go
@@ -0,0 +1,58 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+
+type (
+	CreateRequest struct {
+		Client *clients.Client `json:"client"`
+	}
+	CreateResponse struct {
+		Created *clients.Client `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+	}
+	GetResponse struct {
+		Client *clients.Client `json:"client"`
+	}
+
+	GetByRequest struct {
+		SpaceId string               `json:"space_id"`
+		Config  *clients.GetByParams `json:"config"`
+	}
+	GetByResponse struct {
+		Client *clients.Client `json:"client"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Clients []*clients.Client `json:"clients"`
+	}
+
+	UpdateRequest struct {
+		Client *clients.Client `json:"client"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	EnableRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+		Enable  bool   `json:"enable"`
+	}
+	// Formal exchange type, please do not delete.
+	EnableResponse struct{}
+)
diff --git a/pkg/clients/transport/grpc/client.microgen.go b/pkg/clients/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..421a0178c29b691c33085761bc1710e3c4cbe4eb
--- /dev/null
+++ b/pkg/clients/transport/grpc/client.microgen.go
@@ -0,0 +1,68 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "content.clients.Clients"
+	}
+	return transport.EndpointsSet{
+		CreateEndpoint: grpckit.NewClient(
+			conn, addr, "Create",
+			_Encode_Create_Request,
+			_Decode_Create_Response,
+			pb.CreateResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteEndpoint: grpckit.NewClient(
+			conn, addr, "Delete",
+			_Encode_Delete_Request,
+			_Decode_Delete_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		EnableEndpoint: grpckit.NewClient(
+			conn, addr, "Enable",
+			_Encode_Enable_Request,
+			_Decode_Enable_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		GetByEndpoint: grpckit.NewClient(
+			conn, addr, "GetBy",
+			_Encode_GetBy_Request,
+			_Decode_GetBy_Response,
+			pb.GetByResponse{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		ListEndpoint: grpckit.NewClient(
+			conn, addr, "List",
+			_Encode_List_Request,
+			_Decode_List_Response,
+			pb.ListResponse{},
+			opts...,
+		).Endpoint(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/clients/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/clients/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..fea7765facc0f5ba2c6f07a5c7016d2e326a5d23
--- /dev/null
+++ b/pkg/clients/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,295 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*transport.GetRequest)
+	return &pb.GetRequest{
+		Id:      req.Id,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*transport.ListRequest)
+	return &pb.ListRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*transport.DeleteRequest)
+	return &pb.DeleteRequest{
+		Id:      req.Id,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_Enable_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil EnableRequest")
+	}
+	req := request.(*transport.EnableRequest)
+	return &pb.EnableRequest{
+		Enable:  req.Enable,
+		Id:      req.Id,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*transport.GetResponse)
+	respClient, err := PtrClientToProto(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Client: respClient}, nil
+}
+
+func _Encode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*transport.ListResponse)
+	respClients, err := ListPtrClientToProto(resp.Clients)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Clients: respClients}, nil
+}
+
+func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Enable_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*pb.GetRequest)
+	return &transport.GetRequest{
+		Id:      string(req.Id),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*pb.ListRequest)
+	return &transport.ListRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*pb.DeleteRequest)
+	return &transport.DeleteRequest{
+		Id:      string(req.Id),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Enable_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil EnableRequest")
+	}
+	req := request.(*pb.EnableRequest)
+	return &transport.EnableRequest{
+		Enable:  bool(req.Enable),
+		Id:      string(req.Id),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*pb.GetResponse)
+	respClient, err := ProtoToPtrClient(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Client: respClient}, nil
+}
+
+func _Decode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*pb.ListResponse)
+	respClients, err := ProtoToListPtrClient(resp.Clients)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Clients: respClients}, nil
+}
+
+func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Enable_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	reqClient, err := PtrClientToProto(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Client: reqClient}, nil
+}
+
+func _Encode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*transport.UpdateRequest)
+	reqClient, err := PtrClientToProto(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Client: reqClient}, nil
+}
+
+func _Encode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*transport.CreateResponse)
+	respCreated, err := PtrClientToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Created: respCreated}, nil
+}
+
+func _Decode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*pb.CreateRequest)
+	reqClient, err := ProtoToPtrClient(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Client: reqClient}, nil
+}
+
+func _Decode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*pb.UpdateRequest)
+	reqClient, err := ProtoToPtrClient(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Client: reqClient}, nil
+}
+
+func _Decode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*pb.CreateResponse)
+	respCreated, err := ProtoToPtrClient(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respCreated}, nil
+}
+
+func _Encode_GetBy_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByRequest")
+	}
+	req := request.(*transport.GetByRequest)
+	pbreq := &pb.GetByRequest{SpaceId: req.SpaceId}
+	if req != nil && req.Config != nil {
+		pbreq.ApiKey = req.Config.APIKey
+		pbreq.TlsSubject = req.Config.TLSSubject
+		pbreq.OauthClientId = req.Config.OAuthClientID
+	}
+	return pbreq, nil
+}
+
+func _Encode_GetBy_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByResponse")
+	}
+	resp := response.(*transport.GetByResponse)
+	respClient, err := PtrClientToProto(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetByResponse{Client: respClient}, nil
+}
+
+func _Decode_GetBy_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByRequest")
+	}
+	req := request.(*pb.GetByRequest)
+	return &transport.GetByRequest{
+		Config: &clients.GetByParams{
+			OAuthClientID: req.OauthClientId,
+			APIKey:        req.ApiKey,
+			TLSSubject:    req.TlsSubject,
+		},
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_GetBy_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByResponse")
+	}
+	resp := response.(*pb.GetByResponse)
+	respClient, err := ProtoToPtrClient(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetByResponse{Client: respClient}, nil
+}
diff --git a/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go b/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5212c0b5d2dc9c96533f5f64aee9ed58ab241b41
--- /dev/null
+++ b/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,164 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/clients"
+	permission "git.perx.ru/perxis/perxis-go/pkg/permission"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	commonpb "git.perx.ru/perxis/perxis-go/proto/common"
+)
+
+func ListStringToProto(environments []string) ([]string, error) {
+	return environments, nil
+}
+
+func ProtoToListString(protoEnvironments []string) ([]string, error) {
+	return protoEnvironments, nil
+}
+
+func PtrClientToProto(client *service.Client) (*pb.Client, error) {
+	if client == nil {
+		return nil, nil
+	}
+
+	var oauth *pb.Client_OAuth
+	var tls *pb.Client_TLS
+	var apikey *pb.Client_APIKey
+
+	if client.OAuth != nil {
+		oauth = &pb.Client_OAuth{
+			ClientId:     client.OAuth.ClientID,
+			AuthId:       client.OAuth.AuthID,
+			TokenUrl:     client.OAuth.TokenURL,
+			ClientSecret: client.OAuth.ClientSecret,
+		}
+	}
+	if client.TLS != nil {
+		tls = &pb.Client_TLS{
+			Subject: client.TLS.Subject,
+		}
+	}
+	if client.APIKey != nil {
+		apikey = &pb.Client_APIKey{
+			Key:    client.APIKey.Key,
+			Rotate: client.APIKey.Rotate,
+		}
+	}
+
+	return &pb.Client{
+		Id:          client.ID,
+		SpaceId:     client.SpaceID,
+		Name:        client.Name,
+		Description: client.Description,
+		Disabled:    client.Disabled,
+		RoleId:      client.RoleID,
+		//Environments: client.Environments,
+		//Rules:        rules,
+		Oauth:  oauth,
+		Tls:    tls,
+		ApiKey: apikey,
+	}, nil
+}
+
+func ProtoToPtrClient(protoClient *pb.Client) (*service.Client, error) {
+	if protoClient == nil {
+		return nil, nil
+	}
+
+	var oauth *service.OAuth
+	var tls *service.TLS
+	var apikey *service.APIKey
+
+	if protoClient.Oauth != nil {
+		oauth = &service.OAuth{
+			ClientID:     protoClient.Oauth.ClientId,
+			AuthID:       protoClient.Oauth.AuthId,
+			TokenURL:     protoClient.Oauth.TokenUrl,
+			ClientSecret: protoClient.Oauth.ClientSecret,
+		}
+	}
+	if protoClient.Tls != nil {
+		tls = &service.TLS{
+			Subject: protoClient.Tls.Subject,
+		}
+	}
+	if protoClient.ApiKey != nil {
+		apikey = &service.APIKey{
+			Key:    protoClient.ApiKey.Key,
+			Rotate: protoClient.ApiKey.Rotate,
+		}
+	}
+
+	return &service.Client{
+		ID:          protoClient.Id,
+		SpaceID:     protoClient.SpaceId,
+		Name:        protoClient.Name,
+		Description: protoClient.Description,
+		Disabled:    protoClient.Disabled,
+		RoleID:      protoClient.RoleId,
+		OAuth:       oauth,
+		TLS:         tls,
+		APIKey:      apikey,
+	}, nil
+}
+
+func ListPtrClientToProto(clients []*service.Client) ([]*pb.Client, error) {
+	protoClients := make([]*pb.Client, 0, len(clients))
+	for _, c := range clients {
+		protoClient, _ := PtrClientToProto(c)
+		protoClients = append(protoClients, protoClient)
+	}
+	return protoClients, nil
+}
+
+func ProtoToListPtrClient(protoClients []*pb.Client) ([]*service.Client, error) {
+	clients := make([]*service.Client, 0, len(protoClients))
+	for _, c := range protoClients {
+		client, _ := ProtoToPtrClient(c)
+		clients = append(clients, client)
+	}
+	return clients, nil
+}
+
+func PtrPermissionRuleToProto(rule *permission.Rule) (*commonpb.Rule, error) {
+	if rule == nil {
+		return nil, nil
+	}
+	actions := make([]commonpb.Action, 0, len(rule.Actions))
+	for _, a := range rule.Actions {
+		actions = append(actions, commonpb.Action(a))
+	}
+	return &commonpb.Rule{
+		CollectionId:    rule.CollectionID,
+		Actions:         actions,
+		Access:          commonpb.Access(rule.Access),
+		HiddenFields:    rule.HiddenFields,
+		ReadonlyFields:  rule.ReadonlyFields,
+		WriteonlyFields: rule.WriteonlyFields,
+		ReadFilter:      rule.ReadFilter,
+		WriteFilter:     rule.WriteFilter,
+	}, nil
+}
+
+func ProtoToPtrPermissionRule(protoRule *commonpb.Rule) (*permission.Rule, error) {
+	if protoRule == nil {
+		return nil, nil
+	}
+	actions := make([]permission.Action, 0, len(protoRule.Actions))
+	for _, a := range protoRule.Actions {
+		actions = append(actions, permission.Action(a))
+	}
+	return &permission.Rule{
+		CollectionID:    protoRule.CollectionId,
+		Actions:         actions,
+		Access:          permission.Access(protoRule.Access),
+		HiddenFields:    protoRule.HiddenFields,
+		ReadonlyFields:  protoRule.ReadonlyFields,
+		WriteonlyFields: protoRule.WriteonlyFields,
+		ReadFilter:      protoRule.ReadFilter,
+		WriteFilter:     protoRule.WriteFilter,
+	}, nil
+}
diff --git a/pkg/clients/transport/grpc/server.microgen.go b/pkg/clients/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..7408e691dd4fb1e8e772ffe9e530fcd112d02729
--- /dev/null
+++ b/pkg/clients/transport/grpc/server.microgen.go
@@ -0,0 +1,127 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type clientsServer struct {
+	create grpc.Handler
+	get    grpc.Handler
+	getBy  grpc.Handler
+	list   grpc.Handler
+	update grpc.Handler
+	delete grpc.Handler
+	enable grpc.Handler
+
+	pb.UnimplementedClientsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.ClientsServer {
+	return &clientsServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		enable: grpc.NewServer(
+			endpoints.EnableEndpoint,
+			_Decode_Enable_Request,
+			_Encode_Enable_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		getBy: grpc.NewServer(
+			endpoints.GetByEndpoint,
+			_Decode_GetBy_Request,
+			_Encode_GetBy_Response,
+			opts...,
+		),
+		list: grpc.NewServer(
+			endpoints.ListEndpoint,
+			_Decode_List_Request,
+			_Encode_List_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *clientsServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+	_, resp, err := S.create.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CreateResponse), nil
+}
+
+func (S *clientsServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	_, resp, err := S.get.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetResponse), nil
+}
+
+func (S *clientsServer) GetBy(ctx context.Context, req *pb.GetByRequest) (*pb.GetByResponse, error) {
+	_, resp, err := S.getBy.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetByResponse), nil
+}
+
+func (S *clientsServer) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
+	_, resp, err := S.list.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListResponse), nil
+}
+
+func (S *clientsServer) Update(ctx context.Context, req *pb.UpdateRequest) (*empty.Empty, error) {
+	_, resp, err := S.update.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *clientsServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*empty.Empty, error) {
+	_, resp, err := S.delete.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *clientsServer) Enable(ctx context.Context, req *pb.EnableRequest) (*empty.Empty, error) {
+	_, resp, err := S.enable.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/clients/transport/server.microgen.go b/pkg/clients/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c8b6a3e538dd5d1f28c3e7fd2b9f56af54cae8e
--- /dev/null
+++ b/pkg/clients/transport/server.microgen.go
@@ -0,0 +1,77 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc clients.Clients) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		EnableEndpoint: EnableEndpoint(svc),
+		GetByEndpoint:  GetByEndpoint(svc),
+		GetEndpoint:    GetEndpoint(svc),
+		ListEndpoint:   ListEndpoint(svc),
+		UpdateEndpoint: UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Client)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.Id)
+		return &GetResponse{Client: res0}, res1
+	}
+}
+
+func GetByEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetByRequest)
+		res0, res1 := svc.GetBy(arg0, req.SpaceId, req.Config)
+		return &GetByResponse{Client: res0}, res1
+	}
+}
+
+func ListEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Clients: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Client)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.Id)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func EnableEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*EnableRequest)
+		res0 := svc.Enable(arg0, req.SpaceId, req.Id, req.Enable)
+		return &EnableResponse{}, res0
+	}
+}
diff --git a/pkg/collaborators/collaborator.go b/pkg/collaborators/collaborator.go
new file mode 100644
index 0000000000000000000000000000000000000000..701d8e85b578dafc2036c281c204720080412ac5
--- /dev/null
+++ b/pkg/collaborators/collaborator.go
@@ -0,0 +1,7 @@
+package collaborators
+
+type Collaborator struct {
+	SpaceID string `bson:"spaceId"`
+	Subject string `bson:"subject"`
+	Role    string `bson:"role"`
+}
diff --git a/pkg/collaborators/mocks/Collaborators.go b/pkg/collaborators/mocks/Collaborators.go
new file mode 100644
index 0000000000000000000000000000000000000000..6bcd7fb1c462ae85ef01ca50efa6145ce2f8d591
--- /dev/null
+++ b/pkg/collaborators/mocks/Collaborators.go
@@ -0,0 +1,110 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	collaborators "git.perx.ru/perxis/perxis-go/pkg/collaborators"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Collaborators is an autogenerated mock type for the Collaborators type
+type Collaborators struct {
+	mock.Mock
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, userId
+func (_m *Collaborators) Get(ctx context.Context, spaceId, subject string) (string, error) {
+	ret := _m.Called(ctx, spaceId, subject)
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) string); ok {
+		r0 = rf(ctx, spaceId, subject)
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, subject)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// ListCollaborators provides a mock function with given fields: ctx, spaceId
+func (_m *Collaborators) ListCollaborators(ctx context.Context, spaceId string) ([]*collaborators.Collaborator, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*collaborators.Collaborator
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*collaborators.Collaborator); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*collaborators.Collaborator)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// ListSpaces provides a mock function with given fields: ctx, userId
+func (_m *Collaborators) ListSpaces(ctx context.Context, subject string) ([]*collaborators.Collaborator, error) {
+	ret := _m.Called(ctx, subject)
+
+	var r0 []*collaborators.Collaborator
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*collaborators.Collaborator); ok {
+		r0 = rf(ctx, subject)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*collaborators.Collaborator)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, subject)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Remove provides a mock function with given fields: ctx, spaceId, userId
+func (_m *Collaborators) Remove(ctx context.Context, spaceId, subject string) error {
+	ret := _m.Called(ctx, spaceId, subject)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, subject)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Set provides a mock function with given fields: ctx, spaceId, userId, role
+func (_m *Collaborators) Set(ctx context.Context, spaceId, subject, role string) error {
+	ret := _m.Called(ctx, spaceId, subject, role)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
+		r0 = rf(ctx, spaceId, subject, role)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/collaborators/service.go b/pkg/collaborators/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa56271b89e57a87fc501a11a1ae648f6214a7d5
--- /dev/null
+++ b/pkg/collaborators/service.go
@@ -0,0 +1,26 @@
+package collaborators
+
+import (
+	"context"
+)
+
+// @microgen grpc, recovering, middleware
+// @protobuf git.perx.ru/perxis/perxis-go/proto/collaborators
+// @grpc-addr content.collaborators.Collaborators
+type Collaborators interface {
+
+	// Set - устанавливает участие пользователя в пространстве и его роль
+	Set(ctx context.Context, spaceId, subject, role string) (err error)
+
+	// Get - возвращает роль пользователя в пространстве
+	Get(ctx context.Context, spaceId, subject string) (role string, err error)
+
+	// Remove - удаляет участие пользователя в пространстве
+	Remove(ctx context.Context, spaceId, subject string) (err error)
+
+	// ListCollaborators - возвращает участников пространства и их ролей
+	ListCollaborators(ctx context.Context, spaceId string) (collaborators []*Collaborator, err error)
+
+	// ListSpaces - возвращает список пространств пользователя
+	ListSpaces(ctx context.Context, subject string) (spaces []*Collaborator, err error)
+}
diff --git a/pkg/collaborators/transport/client.microgen.go b/pkg/collaborators/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..eb0dbbe7bc1ea7bad07220fba5cdc6e3b1c9e503
--- /dev/null
+++ b/pkg/collaborators/transport/client.microgen.go
@@ -0,0 +1,82 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	collaborators "git.perx.ru/perxis/perxis-go/pkg/collaborators"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Set(arg0 context.Context, arg1 string, arg2 string, arg3 string) (res0 error) {
+	request := SetRequest{
+		Role:    arg3,
+		SpaceId: arg1,
+		Subject: arg2,
+	}
+	_, res0 = set.SetEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string, arg2 string) (res0 string, res1 error) {
+	request := GetRequest{
+		SpaceId: arg1,
+		Subject: arg2,
+	}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Role, res1
+}
+
+func (set EndpointsSet) Remove(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := RemoveRequest{
+		SpaceId: arg1,
+		Subject: arg2,
+	}
+	_, res0 = set.RemoveEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) ListCollaborators(arg0 context.Context, arg1 string) (res0 []*collaborators.Collaborator, res1 error) {
+	request := ListCollaboratorsRequest{SpaceId: arg1}
+	response, res1 := set.ListCollaboratorsEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListCollaboratorsResponse).Collaborators, res1
+}
+
+func (set EndpointsSet) ListSpaces(arg0 context.Context, arg1 string) (res0 []*collaborators.Collaborator, res1 error) {
+	request := ListSpacesRequest{Subject: arg1}
+	response, res1 := set.ListSpacesEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListSpacesResponse).Spaces, res1
+}
diff --git a/pkg/collaborators/transport/endpoints.microgen.go b/pkg/collaborators/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..022bf403e3a574918d006786c54cfc854f318915
--- /dev/null
+++ b/pkg/collaborators/transport/endpoints.microgen.go
@@ -0,0 +1,14 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Collaborators API and used for transport purposes.
+type EndpointsSet struct {
+	SetEndpoint               endpoint.Endpoint
+	GetEndpoint               endpoint.Endpoint
+	RemoveEndpoint            endpoint.Endpoint
+	ListCollaboratorsEndpoint endpoint.Endpoint
+	ListSpacesEndpoint        endpoint.Endpoint
+}
diff --git a/pkg/collaborators/transport/exchanges.microgen.go b/pkg/collaborators/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..a51674f40f696f15d1f625fbf7923bdd4879e6b5
--- /dev/null
+++ b/pkg/collaborators/transport/exchanges.microgen.go
@@ -0,0 +1,44 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import collaborators "git.perx.ru/perxis/perxis-go/pkg/collaborators"
+
+type (
+	SetRequest struct {
+		SpaceId string `json:"space_id"`
+		Subject string `json:"subject"`
+		Role    string `json:"role"`
+	}
+	// Formal exchange type, please do not delete.
+	SetResponse struct{}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		Subject string `json:"subject"`
+	}
+	GetResponse struct {
+		Role string `json:"role"`
+	}
+
+	RemoveRequest struct {
+		SpaceId string `json:"space_id"`
+		Subject string `json:"subject"`
+	}
+	// Formal exchange type, please do not delete.
+	RemoveResponse struct{}
+
+	ListCollaboratorsRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListCollaboratorsResponse struct {
+		Collaborators []*collaborators.Collaborator `json:"collaborators"`
+	}
+
+	ListSpacesRequest struct {
+		Subject string `json:"subject"`
+	}
+	ListSpacesResponse struct {
+		Spaces []*collaborators.Collaborator `json:"spaces"`
+	}
+)
diff --git a/pkg/collaborators/transport/grpc/client.microgen.go b/pkg/collaborators/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..eaf778f878438fc2a44ef980a25a3aed803fcee8
--- /dev/null
+++ b/pkg/collaborators/transport/grpc/client.microgen.go
@@ -0,0 +1,54 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/collaborators/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/collaborators"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "content.collaborators.Collaborators"
+	}
+	return transport.EndpointsSet{
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		ListCollaboratorsEndpoint: grpckit.NewClient(
+			conn, addr, "ListCollaborators",
+			_Encode_ListCollaborators_Request,
+			_Decode_ListCollaborators_Response,
+			pb.ListCollaboratorsResponse{},
+			opts...,
+		).Endpoint(),
+		ListSpacesEndpoint: grpckit.NewClient(
+			conn, addr, "ListSpaces",
+			_Encode_ListSpaces_Request,
+			_Decode_ListSpaces_Response,
+			pb.ListSpacesResponse{},
+			opts...,
+		).Endpoint(),
+		RemoveEndpoint: grpckit.NewClient(
+			conn, addr, "Remove",
+			_Encode_Remove_Request,
+			_Decode_Remove_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		SetEndpoint: grpckit.NewClient(
+			conn, addr, "Set",
+			_Encode_Set_Request,
+			_Decode_Set_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/collaborators/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/collaborators/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec484dfe3b9a8ee1cb38d1d1d03cbfc5ce7a1f8a
--- /dev/null
+++ b/pkg/collaborators/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,193 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	transport "git.perx.ru/perxis/perxis-go/pkg/collaborators/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/collaborators"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Set_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetRequest")
+	}
+	req := request.(*transport.SetRequest)
+	return &pb.SetRequest{
+		Role:    req.Role,
+		SpaceId: req.SpaceId,
+		Subject: req.Subject,
+	}, nil
+}
+
+func _Encode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*transport.GetRequest)
+	return &pb.GetRequest{
+		SpaceId: req.SpaceId,
+		Subject: req.Subject,
+	}, nil
+}
+
+func _Encode_Remove_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveRequest")
+	}
+	req := request.(*transport.RemoveRequest)
+	return &pb.RemoveRequest{
+		SpaceId: req.SpaceId,
+		Subject: req.Subject,
+	}, nil
+}
+
+func _Encode_ListCollaborators_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListCollaboratorsRequest")
+	}
+	req := request.(*transport.ListCollaboratorsRequest)
+	return &pb.ListCollaboratorsRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_ListSpaces_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListSpacesRequest")
+	}
+	req := request.(*transport.ListSpacesRequest)
+	return &pb.ListSpacesRequest{Subject: req.Subject}, nil
+}
+
+func _Encode_Set_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*transport.GetResponse)
+	return &pb.GetResponse{Role: resp.Role}, nil
+}
+
+func _Encode_Remove_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_ListCollaborators_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListCollaboratorsResponse")
+	}
+	resp := response.(*transport.ListCollaboratorsResponse)
+	respCollaborators, err := ListPtrCollaboratorToProto(resp.Collaborators)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListCollaboratorsResponse{Collaborators: respCollaborators}, nil
+}
+
+func _Encode_ListSpaces_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListSpacesResponse")
+	}
+	resp := response.(*transport.ListSpacesResponse)
+	respSpaces, err := ListPtrCollaboratorToProto(resp.Spaces)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListSpacesResponse{Spaces: respSpaces}, nil
+}
+
+func _Decode_Set_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetRequest")
+	}
+	req := request.(*pb.SetRequest)
+	return &transport.SetRequest{
+		Role:    string(req.Role),
+		SpaceId: string(req.SpaceId),
+		Subject: string(req.Subject),
+	}, nil
+}
+
+func _Decode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*pb.GetRequest)
+	return &transport.GetRequest{
+		SpaceId: string(req.SpaceId),
+		Subject: string(req.Subject),
+	}, nil
+}
+
+func _Decode_Remove_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveRequest")
+	}
+	req := request.(*pb.RemoveRequest)
+	return &transport.RemoveRequest{
+		SpaceId: string(req.SpaceId),
+		Subject: string(req.Subject),
+	}, nil
+}
+
+func _Decode_ListCollaborators_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListCollaboratorsRequest")
+	}
+	req := request.(*pb.ListCollaboratorsRequest)
+	return &transport.ListCollaboratorsRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_ListSpaces_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListSpacesRequest")
+	}
+	req := request.(*pb.ListSpacesRequest)
+	return &transport.ListSpacesRequest{Subject: string(req.Subject)}, nil
+}
+
+func _Decode_Set_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*pb.GetResponse)
+	return &transport.GetResponse{Role: string(resp.Role)}, nil
+}
+
+func _Decode_Remove_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_ListCollaborators_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListCollaboratorsResponse")
+	}
+	resp := response.(*pb.ListCollaboratorsResponse)
+	respCollaborators, err := ProtoToListPtrCollaborator(resp.Collaborators)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListCollaboratorsResponse{Collaborators: respCollaborators}, nil
+}
+
+func _Decode_ListSpaces_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListSpacesResponse")
+	}
+	resp := response.(*pb.ListSpacesResponse)
+	respSpaces, err := ProtoToListPtrCollaborator(resp.Spaces)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListSpacesResponse{Spaces: respSpaces}, nil
+}
diff --git a/pkg/collaborators/transport/grpc/protobuf_type_converters.microgen.go b/pkg/collaborators/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5752acdf6235ab7b1bd09a46fc9ff3c8467ca7df
--- /dev/null
+++ b/pkg/collaborators/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,34 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/collaborators"
+	pbcommon "git.perx.ru/perxis/perxis-go/proto/common"
+)
+
+func ListPtrCollaboratorToProto(collaborators []*service.Collaborator) ([]*pbcommon.Collaborator, error) {
+	protoCollaborators := make([]*pbcommon.Collaborator, 0, len(collaborators))
+	for _, c := range collaborators {
+		protoCollaborators = append(protoCollaborators, &pbcommon.Collaborator{
+			SpaceId: c.SpaceID,
+			Subject: c.Subject,
+			Role:    c.Role,
+		})
+	}
+	return protoCollaborators, nil
+}
+
+func ProtoToListPtrCollaborator(protoCollaborators []*pbcommon.Collaborator) ([]*service.Collaborator, error) {
+	collaborators := make([]*service.Collaborator, 0, len(protoCollaborators))
+	for _, c := range protoCollaborators {
+		collaborators = append(collaborators, &service.Collaborator{
+			SpaceID: c.SpaceId,
+			Subject: c.Subject,
+			Role:    c.Role,
+		})
+	}
+	return collaborators, nil
+}
diff --git a/pkg/collaborators/transport/grpc/server.microgen.go b/pkg/collaborators/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6ece1b8b7d40f2c78d178ea88cf2aeb2ff7bb44f
--- /dev/null
+++ b/pkg/collaborators/transport/grpc/server.microgen.go
@@ -0,0 +1,97 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/collaborators/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/collaborators"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type collaboratorsServer struct {
+	set               grpc.Handler
+	get               grpc.Handler
+	remove            grpc.Handler
+	listCollaborators grpc.Handler
+	listSpaces        grpc.Handler
+
+	pb.UnimplementedCollaboratorsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.CollaboratorsServer {
+	return &collaboratorsServer{
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		listCollaborators: grpc.NewServer(
+			endpoints.ListCollaboratorsEndpoint,
+			_Decode_ListCollaborators_Request,
+			_Encode_ListCollaborators_Response,
+			opts...,
+		),
+		listSpaces: grpc.NewServer(
+			endpoints.ListSpacesEndpoint,
+			_Decode_ListSpaces_Request,
+			_Encode_ListSpaces_Response,
+			opts...,
+		),
+		remove: grpc.NewServer(
+			endpoints.RemoveEndpoint,
+			_Decode_Remove_Request,
+			_Encode_Remove_Response,
+			opts...,
+		),
+		set: grpc.NewServer(
+			endpoints.SetEndpoint,
+			_Decode_Set_Request,
+			_Encode_Set_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *collaboratorsServer) Set(ctx context.Context, req *pb.SetRequest) (*empty.Empty, error) {
+	_, resp, err := S.set.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *collaboratorsServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	_, resp, err := S.get.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetResponse), nil
+}
+
+func (S *collaboratorsServer) Remove(ctx context.Context, req *pb.RemoveRequest) (*empty.Empty, error) {
+	_, resp, err := S.remove.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *collaboratorsServer) ListCollaborators(ctx context.Context, req *pb.ListCollaboratorsRequest) (*pb.ListCollaboratorsResponse, error) {
+	_, resp, err := S.listCollaborators.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListCollaboratorsResponse), nil
+}
+
+func (S *collaboratorsServer) ListSpaces(ctx context.Context, req *pb.ListSpacesRequest) (*pb.ListSpacesResponse, error) {
+	_, resp, err := S.listSpaces.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListSpacesResponse), nil
+}
diff --git a/pkg/collaborators/transport/server.microgen.go b/pkg/collaborators/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..7d0a571d32ebe2ec2a49190a5d79136ff96ba8c1
--- /dev/null
+++ b/pkg/collaborators/transport/server.microgen.go
@@ -0,0 +1,60 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	collaborators "git.perx.ru/perxis/perxis-go/pkg/collaborators"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc collaborators.Collaborators) EndpointsSet {
+	return EndpointsSet{
+		GetEndpoint:               GetEndpoint(svc),
+		ListCollaboratorsEndpoint: ListCollaboratorsEndpoint(svc),
+		ListSpacesEndpoint:        ListSpacesEndpoint(svc),
+		RemoveEndpoint:            RemoveEndpoint(svc),
+		SetEndpoint:               SetEndpoint(svc),
+	}
+}
+
+func SetEndpoint(svc collaborators.Collaborators) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*SetRequest)
+		res0 := svc.Set(arg0, req.SpaceId, req.Subject, req.Role)
+		return &SetResponse{}, res0
+	}
+}
+
+func GetEndpoint(svc collaborators.Collaborators) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.Subject)
+		return &GetResponse{Role: res0}, res1
+	}
+}
+
+func RemoveEndpoint(svc collaborators.Collaborators) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*RemoveRequest)
+		res0 := svc.Remove(arg0, req.SpaceId, req.Subject)
+		return &RemoveResponse{}, res0
+	}
+}
+
+func ListCollaboratorsEndpoint(svc collaborators.Collaborators) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListCollaboratorsRequest)
+		res0, res1 := svc.ListCollaborators(arg0, req.SpaceId)
+		return &ListCollaboratorsResponse{Collaborators: res0}, res1
+	}
+}
+
+func ListSpacesEndpoint(svc collaborators.Collaborators) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListSpacesRequest)
+		res0, res1 := svc.ListSpaces(arg0, req.Subject)
+		return &ListSpacesResponse{Spaces: res0}, res1
+	}
+}
diff --git a/pkg/environments/environment.go b/pkg/environments/environment.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbd468dc361951e048a0840f4158a42c962e7af1
--- /dev/null
+++ b/pkg/environments/environment.go
@@ -0,0 +1,114 @@
+package environments
+
+import "time"
+
+const (
+	DefaultEnvironment = "master"
+)
+
+type State int
+
+const (
+	StateUnknown State = iota
+	StateNew
+	StatePreparing
+	StateReady
+	StateError
+
+	StateInfoEmpty = "EMPTY"
+)
+
+func (s State) String() string {
+	switch s {
+	case StateNew:
+		return "new"
+	case StatePreparing:
+		return "preparing"
+	case StateReady:
+		return "ready"
+	case StateError:
+		return "error"
+	default:
+		return "unknown"
+	}
+}
+
+type StateInfo struct {
+	State     State     `json:"state" bson:"state"`
+	StartedAt time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"`
+	Info      string    `json:"info,omitempty" bson:"info,omitempty"`
+}
+
+type Config struct {
+	SourceID string
+
+	// Deprecated
+	Features []string
+}
+
+// Environment - представляет рабочее окружения для пространства
+// Каждое окружение может иметь собственный набор коллекций и данных и
+// использоваться независимо друг от друга
+type Environment struct {
+	ID          string `json:"id" bson:"_id"` // Идентификатор окружения, задается пользователем при создании. Уникален в рамках пространства `SpaceID`
+	SpaceID     string `json:"spaceID" bson:"-"`
+	Description string `json:"description" bson:"desc,omitempty"` // Описание для окружения
+	//State       State  `json:"state" bson:"state"`                // Состояние окружения (Preparing/Ready/Failed)
+	//StateInfo   string   `json:"state_info,omitempty" bson:"state_info,omitempty"`
+
+	// StateInfo отображает состояние коллекции:
+	// - State: идентификатор состояния окружения (unknown/new/preparing/ready/error)
+	// - Info: дополнительная информация о состоянии коллекции (например, если при
+	//   применении схемы к коллекции произошла ошибка)
+	// - StartedAt: время, в которое коллекция перешла в состояние `Preparing`
+	StateInfo *StateInfo `json:"state_info" bson:"state_info,omitempty"`
+
+	Aliases []string `json:"aliases" bson:"aliases,omitempty"` // Синонимы окружения (только чтение)
+	Config  *Config  `json:"config,omitempty" bson:"config,omitempty"`
+}
+
+func (e Environment) Clone() *Environment {
+	clone := &Environment{
+		ID:          e.ID,
+		SpaceID:     e.SpaceID,
+		Description: e.Description,
+		Aliases:     append([]string(nil), e.Aliases...),
+		Config:      nil,
+	}
+
+	if e.StateInfo != nil {
+		clone.StateInfo = &StateInfo{
+			State:     e.StateInfo.State,
+			Info:      e.StateInfo.Info,
+			StartedAt: e.StateInfo.StartedAt,
+		}
+	}
+
+	if e.Config != nil {
+		clone.Config = &Config{
+			SourceID: e.Config.SourceID,
+		}
+	}
+
+	return clone
+}
+
+func (e Environment) Fetch(i interface{}) interface{} {
+	p, _ := i.(string)
+	switch p {
+	case "ID":
+		return e.ID
+	case "SpaceID":
+		return e.SpaceID
+	case "Description":
+		return e.Description
+	case "StateInfo":
+		return e.StateInfo
+	case "Aliases":
+		return e.Aliases
+	case "Config":
+		return e.Config
+	default:
+		panic("unknown parameter")
+	}
+}
diff --git a/pkg/environments/mocks/Environments.go b/pkg/environments/mocks/Environments.go
new file mode 100644
index 0000000000000000000000000000000000000000..8c8d099f32cbcbd7cd1edf377b3d46e6756a1d72
--- /dev/null
+++ b/pkg/environments/mocks/Environments.go
@@ -0,0 +1,176 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Environments is an autogenerated mock type for the Environments type
+type Environments struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, env
+func (_m *Environments) Create(ctx context.Context, env *environments.Environment) (*environments.Environment, error) {
+	ret := _m.Called(ctx, env)
+
+	var r0 *environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, *environments.Environment) *environments.Environment); ok {
+		r0 = rf(ctx, env)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *environments.Environment) error); ok {
+		r1 = rf(ctx, env)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, envId
+func (_m *Environments) Delete(ctx context.Context, spaceId string, envId string) error {
+	ret := _m.Called(ctx, spaceId, envId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, envId
+func (_m *Environments) Get(ctx context.Context, spaceId string, envId string) (*environments.Environment, error) {
+	ret := _m.Called(ctx, spaceId, envId)
+
+	var r0 *environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *environments.Environment); ok {
+		r0 = rf(ctx, spaceId, envId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, envId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Environments) List(ctx context.Context, spaceId string) ([]*environments.Environment, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*environments.Environment); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Migrate provides a mock function with given fields: ctx, spaceId, envId, options
+func (_m *Environments) Migrate(ctx context.Context, spaceId string, envId string, options ...*environments.MigrateOptions) error {
+	_va := make([]interface{}, len(options))
+	for _i := range options {
+		_va[_i] = options[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, spaceId, envId)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, ...*environments.MigrateOptions) error); ok {
+		r0 = rf(ctx, spaceId, envId, options...)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// RemoveAlias provides a mock function with given fields: ctx, spaceId, envId, alias
+func (_m *Environments) RemoveAlias(ctx context.Context, spaceId string, envId string, alias string) error {
+	ret := _m.Called(ctx, spaceId, envId, alias)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId, alias)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// SetAlias provides a mock function with given fields: ctx, spaceId, envId, alias
+func (_m *Environments) SetAlias(ctx context.Context, spaceId string, envId string, alias string) error {
+	ret := _m.Called(ctx, spaceId, envId, alias)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId, alias)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Update provides a mock function with given fields: ctx, env
+func (_m *Environments) Update(ctx context.Context, env *environments.Environment) error {
+	ret := _m.Called(ctx, env)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *environments.Environment) error); ok {
+		r0 = rf(ctx, env)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewEnvironments interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewEnvironments creates a new instance of Environments. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewEnvironments(t mockConstructorTestingTNewEnvironments) *Environments {
+	mock := &Environments{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/environments/options.go b/pkg/environments/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..02f1a1fea3229ccfe702401c2dfa06e5094d9671
--- /dev/null
+++ b/pkg/environments/options.go
@@ -0,0 +1,37 @@
+package environments
+
+type MigrateOptions struct {
+
+	// Ожидать завершения миграции в синхронном режиме
+	Wait bool
+}
+
+func MergeMigrateOptions(opts ...*MigrateOptions) *MigrateOptions {
+	o := &MigrateOptions{}
+	for _, opt := range opts {
+		if opt.Wait {
+			o.Wait = true
+		}
+	}
+	return o
+}
+
+type UpdateOptions struct {
+
+	// Состояние будет обновлено только в том случае, если выполняется указанное условие
+	// Cond указывается с использованием синтаксиса `expr`
+	Cond string
+}
+
+func MergeUpdateOptions(opts ...*UpdateOptions) *UpdateOptions {
+	o := &UpdateOptions{}
+	for _, opt := range opts {
+		if opt.Cond != "" {
+			if o.Cond != "" {
+				o.Cond += " && "
+			}
+			o.Cond += opt.Cond
+		}
+	}
+	return o
+}
diff --git a/pkg/environments/service.go b/pkg/environments/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd0a37f449819a4b92239f8d8b38a3a0036ec8d8
--- /dev/null
+++ b/pkg/environments/service.go
@@ -0,0 +1,20 @@
+package environments
+
+import (
+	"context"
+)
+
+// Environments
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/environments
+// @grpc-addr content.environments.Environments
+type Environments interface {
+	Create(ctx context.Context, env *Environment) (created *Environment, err error)
+	Get(ctx context.Context, spaceId, envId string) (env *Environment, err error)
+	List(ctx context.Context, spaceId string) (envs []*Environment, err error)
+	Update(ctx context.Context, env *Environment) (err error)
+	Delete(ctx context.Context, spaceId, envId string) (err error)
+	SetAlias(ctx context.Context, spaceId, envId, alias string) (err error)
+	RemoveAlias(ctx context.Context, spaceId, envId, alias string) (err error)
+	Migrate(ctx context.Context, spaceId, envId string, options ...*MigrateOptions) (err error)
+}
diff --git a/pkg/environments/transport/client.microgen.go b/pkg/environments/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..96094fda943e407d0264a8abc738fd5c3afe440e
--- /dev/null
+++ b/pkg/environments/transport/client.microgen.go
@@ -0,0 +1,126 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *environments.Environment) (res0 *environments.Environment, res1 error) {
+	request := CreateRequest{Env: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string, arg2 string) (res0 *environments.Environment, res1 error) {
+	request := GetRequest{
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Env, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*environments.Environment, res1 error) {
+	request := ListRequest{SpaceId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListResponse).Envs, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *environments.Environment) (res0 error) {
+	request := UpdateRequest{Env: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) SetAlias(arg0 context.Context, arg1 string, arg2 string, arg3 string) (res0 error) {
+	request := SetAliasRequest{
+		Alias:   arg3,
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.SetAliasEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) RemoveAlias(arg0 context.Context, arg1 string, arg2 string, arg3 string) (res0 error) {
+	request := RemoveAliasRequest{
+		Alias:   arg3,
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.RemoveAliasEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Migrate(arg0 context.Context, arg1 string, arg2 string, arg3 ...*environments.MigrateOptions) (res0 error) {
+	request := MigrateRequest{
+		EnvId:   arg2,
+		Options: arg3,
+		SpaceId: arg1,
+	}
+	_, res0 = set.MigrateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/environments/transport/endpoints.microgen.go b/pkg/environments/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5639637d2b479721e21d5c3fe4ea097d2f2a6cde
--- /dev/null
+++ b/pkg/environments/transport/endpoints.microgen.go
@@ -0,0 +1,17 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Environments API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint      endpoint.Endpoint
+	GetEndpoint         endpoint.Endpoint
+	ListEndpoint        endpoint.Endpoint
+	UpdateEndpoint      endpoint.Endpoint
+	DeleteEndpoint      endpoint.Endpoint
+	SetAliasEndpoint    endpoint.Endpoint
+	RemoveAliasEndpoint endpoint.Endpoint
+	MigrateEndpoint     endpoint.Endpoint
+}
diff --git a/pkg/environments/transport/exchanges.microgen.go b/pkg/environments/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1a0ab068d575767c543d8db652c5d8c9a7e02f4
--- /dev/null
+++ b/pkg/environments/transport/exchanges.microgen.go
@@ -0,0 +1,66 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+
+type (
+	CreateRequest struct {
+		Env *environments.Environment `json:"env"`
+	}
+	CreateResponse struct {
+		Created *environments.Environment `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+	}
+	GetResponse struct {
+		Env *environments.Environment `json:"env"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Envs []*environments.Environment `json:"envs"`
+	}
+
+	UpdateRequest struct {
+		Env *environments.Environment `json:"env"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	SetAliasRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+		Alias   string `json:"alias"`
+	}
+	// Formal exchange type, please do not delete.
+	SetAliasResponse struct{}
+
+	RemoveAliasRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+		Alias   string `json:"alias"`
+	}
+	// Formal exchange type, please do not delete.
+	RemoveAliasResponse struct{}
+
+	MigrateRequest struct {
+		SpaceId string                         `json:"space_id"`
+		EnvId   string                         `json:"env_id"`
+		Options []*environments.MigrateOptions `json:"options"` // This field was defined with ellipsis (...).
+	}
+	// Formal exchange type, please do not delete.
+	MigrateResponse struct{}
+)
diff --git a/pkg/environments/transport/grpc/client.microgen.go b/pkg/environments/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..34e0fa4db1cf8149475dde562b3179091089f70f
--- /dev/null
+++ b/pkg/environments/transport/grpc/client.microgen.go
@@ -0,0 +1,75 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "content.environments.Environments"
+	}
+	return transport.EndpointsSet{
+		CreateEndpoint: grpckit.NewClient(
+			conn, addr, "Create",
+			_Encode_Create_Request,
+			_Decode_Create_Response,
+			pb.CreateResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteEndpoint: grpckit.NewClient(
+			conn, addr, "Delete",
+			_Encode_Delete_Request,
+			_Decode_Delete_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		ListEndpoint: grpckit.NewClient(
+			conn, addr, "List",
+			_Encode_List_Request,
+			_Decode_List_Response,
+			pb.ListResponse{},
+			opts...,
+		).Endpoint(),
+		MigrateEndpoint: grpckit.NewClient(
+			conn, addr, "Migrate",
+			_Encode_Migrate_Request,
+			_Decode_Migrate_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		RemoveAliasEndpoint: grpckit.NewClient(
+			conn, addr, "RemoveAlias",
+			_Encode_RemoveAlias_Request,
+			_Decode_RemoveAlias_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		SetAliasEndpoint: grpckit.NewClient(
+			conn, addr, "SetAlias",
+			_Encode_SetAlias_Request,
+			_Decode_SetAlias_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/environments/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/environments/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6411216cd4f54cedd9bf2a3c61ff77b98d70cc35
--- /dev/null
+++ b/pkg/environments/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,307 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	transport "git.perx.ru/perxis/perxis-go/pkg/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	reqEnv, err := PtrEnvironmentToProto(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Env: reqEnv}, nil
+}
+
+func _Encode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*transport.GetRequest)
+	return &pb.GetRequest{
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*transport.ListRequest)
+	return &pb.ListRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*transport.UpdateRequest)
+	reqEnv, err := PtrEnvironmentToProto(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Env: reqEnv}, nil
+}
+
+func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*transport.DeleteRequest)
+	return &pb.DeleteRequest{
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_SetAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetAliasRequest")
+	}
+	req := request.(*transport.SetAliasRequest)
+	return &pb.SetAliasRequest{
+		Alias:   req.Alias,
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_RemoveAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveAliasRequest")
+	}
+	req := request.(*transport.RemoveAliasRequest)
+	return &pb.RemoveAliasRequest{
+		Alias:   req.Alias,
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*transport.CreateResponse)
+	respCreated, err := PtrEnvironmentToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Created: respCreated}, nil
+}
+
+func _Encode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*transport.GetResponse)
+	respEnv, err := PtrEnvironmentToProto(resp.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Env: respEnv}, nil
+}
+
+func _Encode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*transport.ListResponse)
+	respEnvs, err := ListPtrEnvironmentToProto(resp.Envs)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Envs: respEnvs}, nil
+}
+
+func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_SetAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_RemoveAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*pb.CreateRequest)
+	reqEnv, err := ProtoToPtrEnvironment(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Env: reqEnv}, nil
+}
+
+func _Decode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*pb.GetRequest)
+	return &transport.GetRequest{
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*pb.ListRequest)
+	return &transport.ListRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*pb.UpdateRequest)
+	reqEnv, err := ProtoToPtrEnvironment(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Env: reqEnv}, nil
+}
+
+func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*pb.DeleteRequest)
+	return &transport.DeleteRequest{
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_SetAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetAliasRequest")
+	}
+	req := request.(*pb.SetAliasRequest)
+	return &transport.SetAliasRequest{
+		Alias:   string(req.Alias),
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_RemoveAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveAliasRequest")
+	}
+	req := request.(*pb.RemoveAliasRequest)
+	return &transport.RemoveAliasRequest{
+		Alias:   string(req.Alias),
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*pb.CreateResponse)
+	respCreated, err := ProtoToPtrEnvironment(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respCreated}, nil
+}
+
+func _Decode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*pb.GetResponse)
+	respEnv, err := ProtoToPtrEnvironment(resp.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Env: respEnv}, nil
+}
+
+func _Decode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*pb.ListResponse)
+	respEnvs, err := ProtoToListPtrEnvironment(resp.Envs)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Envs: respEnvs}, nil
+}
+
+func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_SetAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_RemoveAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Migrate_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MigrateRequest")
+	}
+	req := request.(*transport.MigrateRequest)
+	opts, _ := ElPtrMigrateOptionsToProto(req.Options)
+	return &pb.MigrateRequest{
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+		Options: opts,
+	}, nil
+}
+
+func _Encode_Migrate_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Migrate_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MigrateRequest")
+	}
+	req := request.(*pb.MigrateRequest)
+	opts, _ := ProtoToElPtrMigrateOptions(req.Options)
+	return &transport.MigrateRequest{
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+		Options: opts,
+	}, nil
+}
+
+func _Decode_Migrate_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go b/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5402c513bc7ecbf3a7dc3b4a291282eed03f554e
--- /dev/null
+++ b/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,99 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	service "git.perx.ru/perxis/perxis-go/pkg/environments"
+	"github.com/golang/protobuf/ptypes"
+)
+
+func PtrEnvironmentToProto(env *service.Environment) (*pb.Environment, error) {
+	if env == nil {
+		return nil, nil
+	}
+	protoEnvironment := &pb.Environment{
+		Id:          env.ID,
+		SpaceId:     env.SpaceID,
+		Description: env.Description,
+		Aliases:     env.Aliases,
+	}
+	if env.StateInfo != nil {
+		protoEnvironment.StateInfo = &pb.StateInfo{
+			State: pb.StateInfo_State(env.StateInfo.State),
+			Info:  env.StateInfo.Info,
+		}
+		protoEnvironment.StateInfo.StartedAt, _ = ptypes.TimestampProto(env.StateInfo.StartedAt)
+	}
+	if env.Config != nil {
+		protoEnvironment.Config = &pb.Config{
+			SourceId: env.Config.SourceID,
+			Features: env.Config.Features,
+		}
+	}
+	return protoEnvironment, nil
+}
+
+func ProtoToPtrEnvironment(protoEnv *pb.Environment) (*service.Environment, error) {
+	if protoEnv == nil {
+		return nil, nil
+	}
+	env := &service.Environment{
+		ID:          protoEnv.Id,
+		SpaceID:     protoEnv.SpaceId,
+		Description: protoEnv.Description,
+		Aliases:     protoEnv.Aliases,
+	}
+	if protoEnv.StateInfo != nil {
+		env.StateInfo = &service.StateInfo{
+			State: service.State(protoEnv.StateInfo.State),
+			Info:  protoEnv.StateInfo.Info,
+		}
+		env.StateInfo.StartedAt, _ = ptypes.Timestamp(protoEnv.StateInfo.StartedAt)
+	}
+	if protoEnv.Config != nil {
+		env.Config = &service.Config{
+			SourceID: protoEnv.Config.SourceId,
+			Features: protoEnv.Config.Features,
+		}
+	}
+	return env, nil
+}
+
+func ListPtrEnvironmentToProto(envs []*service.Environment) ([]*pb.Environment, error) {
+	protoEnvironments := make([]*pb.Environment, 0, len(envs))
+	for _, environment := range envs {
+		protoEnvironment, err := PtrEnvironmentToProto(environment)
+		if err != nil {
+			return nil, err
+		}
+		protoEnvironments = append(protoEnvironments, protoEnvironment)
+	}
+	return protoEnvironments, nil
+}
+
+func ProtoToListPtrEnvironment(protoEnvs []*pb.Environment) ([]*service.Environment, error) {
+	environments := make([]*service.Environment, 0, len(protoEnvs))
+	for _, protoEnvironment := range protoEnvs {
+		environment, err := ProtoToPtrEnvironment(protoEnvironment)
+		if err != nil {
+			return nil, err
+		}
+		environments = append(environments, environment)
+	}
+	return environments, nil
+}
+
+func ElPtrMigrateOptionsToProto(options []*service.MigrateOptions) (*pb.MigrateOptions, error) {
+	opts := service.MergeMigrateOptions(options...)
+	return &pb.MigrateOptions{Wait: opts.Wait}, nil
+}
+
+func ProtoToElPtrMigrateOptions(protoOptions *pb.MigrateOptions) ([]*service.MigrateOptions, error) {
+	if protoOptions == nil {
+		return nil, nil
+	}
+	return []*service.MigrateOptions{{Wait: protoOptions.Wait}}, nil
+}
diff --git a/pkg/environments/transport/grpc/server.microgen.go b/pkg/environments/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..1389d32692eaf27f5068073d330696d1ad221bd3
--- /dev/null
+++ b/pkg/environments/transport/grpc/server.microgen.go
@@ -0,0 +1,142 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type environmentsServer struct {
+	create      grpc.Handler
+	get         grpc.Handler
+	list        grpc.Handler
+	update      grpc.Handler
+	delete      grpc.Handler
+	setAlias    grpc.Handler
+	removeAlias grpc.Handler
+	migrate     grpc.Handler
+
+	pb.UnimplementedEnvironmentsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.EnvironmentsServer {
+	return &environmentsServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		list: grpc.NewServer(
+			endpoints.ListEndpoint,
+			_Decode_List_Request,
+			_Encode_List_Response,
+			opts...,
+		),
+		migrate: grpc.NewServer(
+			endpoints.MigrateEndpoint,
+			_Decode_Migrate_Request,
+			_Encode_Migrate_Response,
+			opts...,
+		),
+		removeAlias: grpc.NewServer(
+			endpoints.RemoveAliasEndpoint,
+			_Decode_RemoveAlias_Request,
+			_Encode_RemoveAlias_Response,
+			opts...,
+		),
+		setAlias: grpc.NewServer(
+			endpoints.SetAliasEndpoint,
+			_Decode_SetAlias_Request,
+			_Encode_SetAlias_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *environmentsServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+	_, resp, err := S.create.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CreateResponse), nil
+}
+
+func (S *environmentsServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	_, resp, err := S.get.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetResponse), nil
+}
+
+func (S *environmentsServer) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
+	_, resp, err := S.list.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListResponse), nil
+}
+
+func (S *environmentsServer) Update(ctx context.Context, req *pb.UpdateRequest) (*empty.Empty, error) {
+	_, resp, err := S.update.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*empty.Empty, error) {
+	_, resp, err := S.delete.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) SetAlias(ctx context.Context, req *pb.SetAliasRequest) (*empty.Empty, error) {
+	_, resp, err := S.setAlias.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) RemoveAlias(ctx context.Context, req *pb.RemoveAliasRequest) (*empty.Empty, error) {
+	_, resp, err := S.removeAlias.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) Migrate(ctx context.Context, req *pb.MigrateRequest) (*empty.Empty, error) {
+	_, resp, err := S.migrate.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/environments/transport/server.microgen.go b/pkg/environments/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..3a0b20a55cdf3aff82beb803efb59c5833e5afd7
--- /dev/null
+++ b/pkg/environments/transport/server.microgen.go
@@ -0,0 +1,88 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+
+"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc environments.Environments) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint:      CreateEndpoint(svc),
+		DeleteEndpoint:      DeleteEndpoint(svc),
+		GetEndpoint:         GetEndpoint(svc),
+		ListEndpoint:        ListEndpoint(svc),
+		MigrateEndpoint:     MigrateEndpoint(svc),
+		RemoveAliasEndpoint: RemoveAliasEndpoint(svc),
+		SetAliasEndpoint:    SetAliasEndpoint(svc),
+		UpdateEndpoint:      UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Env)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.EnvId)
+		return &GetResponse{Env: res0}, res1
+	}
+}
+
+func ListEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Envs: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Env)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.EnvId)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func SetAliasEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*SetAliasRequest)
+		res0 := svc.SetAlias(arg0, req.SpaceId, req.EnvId, req.Alias)
+		return &SetAliasResponse{}, res0
+	}
+}
+
+func RemoveAliasEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*RemoveAliasRequest)
+		res0 := svc.RemoveAlias(arg0, req.SpaceId, req.EnvId, req.Alias)
+		return &RemoveAliasResponse{}, res0
+	}
+}
+
+func MigrateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*MigrateRequest)
+		res0 := svc.Migrate(arg0, req.SpaceId, req.EnvId, req.Options...)
+		return &MigrateResponse{}, res0
+	}
+}
diff --git a/pkg/locales/locale.go b/pkg/locales/locale.go
new file mode 100644
index 0000000000000000000000000000000000000000..d3cc43c6c625a5090d975409d7aa2beb16eefc3a
--- /dev/null
+++ b/pkg/locales/locale.go
@@ -0,0 +1,7 @@
+package locales
+
+type Locale struct {
+	ID      string `json:"id" bson:"_id"` // (Пример: "en", "en-US")
+	SpaceID string `json:"spaceId" bson:"-"`
+	Name    string `json:"name" bson:"name"` // (Пример: "English", "English (US)" )
+}
diff --git a/pkg/locales/mocks/Locales.go b/pkg/locales/mocks/Locales.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e63dfedfff975f52f886d19e1e8982bccb96b7e
--- /dev/null
+++ b/pkg/locales/mocks/Locales.go
@@ -0,0 +1,75 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Locales is an autogenerated mock type for the Locales type
+type Locales struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, locale
+func (_m *Locales) Create(ctx context.Context, locale *locales.Locale) (*locales.Locale, error) {
+	ret := _m.Called(ctx, locale)
+
+	var r0 *locales.Locale
+	if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) *locales.Locale); ok {
+		r0 = rf(ctx, locale)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*locales.Locale)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *locales.Locale) error); ok {
+		r1 = rf(ctx, locale)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, localeId
+func (_m *Locales) Delete(ctx context.Context, spaceId string, localeId string) error {
+	ret := _m.Called(ctx, spaceId, localeId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, localeId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Locales) List(ctx context.Context, spaceId string) ([]*locales.Locale, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*locales.Locale
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*locales.Locale); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*locales.Locale)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/locales/service.go b/pkg/locales/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..7724d7f7ecdb2f4733d97bff01113d105adabdeb
--- /dev/null
+++ b/pkg/locales/service.go
@@ -0,0 +1,14 @@
+package locales
+
+import (
+	"context"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/locales
+// @grpc-addr content.locales.Locales
+type Locales interface {
+	Create(ctx context.Context, locale *Locale) (created *Locale, err error)
+	List(ctx context.Context, spaceId string) (locales []*Locale, err error)
+	Delete(ctx context.Context, spaceId, localeId string) (err error)
+}
diff --git a/pkg/locales/transport/client.microgen.go b/pkg/locales/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8cd9dee23dafeff1d635b3f5ee42f6e5c8667f1
--- /dev/null
+++ b/pkg/locales/transport/client.microgen.go
@@ -0,0 +1,51 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *locales.Locale) (res0 *locales.Locale, res1 error) {
+	request := CreateRequest{Locale: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*locales.Locale, res1 error) {
+	request := ListRequest{SpaceId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListResponse).Locales, res1
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		LocaleId: arg2,
+		SpaceId:  arg1,
+	}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/locales/transport/endpoints.microgen.go b/pkg/locales/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffca7318747104f8b58af332e90646f8cc6a8b9c
--- /dev/null
+++ b/pkg/locales/transport/endpoints.microgen.go
@@ -0,0 +1,12 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Locales API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	ListEndpoint   endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+}
diff --git a/pkg/locales/transport/exchanges.microgen.go b/pkg/locales/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..a07204e13a233fd7bae40c74e9984061871a92b7
--- /dev/null
+++ b/pkg/locales/transport/exchanges.microgen.go
@@ -0,0 +1,28 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+
+type (
+	CreateRequest struct {
+		Locale *locales.Locale `json:"locale"`
+	}
+	CreateResponse struct {
+		Created *locales.Locale `json:"created"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Locales []*locales.Locale `json:"locales"`
+	}
+
+	DeleteRequest struct {
+		SpaceId  string `json:"space_id"`
+		LocaleId string `json:"locale_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+)
diff --git a/pkg/locales/transport/grpc/client.microgen.go b/pkg/locales/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..3af5bbae0ed86bf079d93622cc8f1aa4f7fbdfd8
--- /dev/null
+++ b/pkg/locales/transport/grpc/client.microgen.go
@@ -0,0 +1,40 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "content.locales.Locales"
+	}
+	return transport.EndpointsSet{
+		CreateEndpoint: grpckit.NewClient(
+			conn, addr, "Create",
+			_Encode_Create_Request,
+			_Decode_Create_Response,
+			pb.CreateResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteEndpoint: grpckit.NewClient(
+			conn, addr, "Delete",
+			_Encode_Delete_Request,
+			_Decode_Delete_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		ListEndpoint: grpckit.NewClient(
+			conn, addr, "List",
+			_Encode_List_Request,
+			_Decode_List_Response,
+			pb.ListResponse{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d06e8232c89f226e4f7c0a80ca6d52eb219d48f
--- /dev/null
+++ b/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,131 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	transport "git.perx.ru/perxis/perxis-go/pkg/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	pbLocale, err := PtrLocaleToProto(req.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Locale: pbLocale}, nil
+}
+
+func _Encode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*transport.ListRequest)
+	return &pb.ListRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*transport.DeleteRequest)
+	return &pb.DeleteRequest{
+		LocaleId: req.LocaleId,
+		SpaceId:  req.SpaceId,
+	}, nil
+}
+
+func _Encode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*transport.CreateResponse)
+	respLocale, err := PtrLocaleToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Locale: respLocale}, nil
+}
+
+func _Encode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*transport.ListResponse)
+	respLocales, err := ListPtrLocaleToProto(resp.Locales)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Locales: respLocales}, nil
+}
+
+func _Encode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*pb.CreateRequest)
+	locale, err := ProtoToPtrLocale(req.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Locale: locale}, nil
+}
+
+func _Decode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*pb.ListRequest)
+	return &transport.ListRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*pb.DeleteRequest)
+	return &transport.DeleteRequest{
+		LocaleId: string(req.LocaleId),
+		SpaceId:  string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*pb.CreateResponse)
+	respLocale, err := ProtoToPtrLocale(resp.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respLocale}, nil
+}
+
+func _Decode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*pb.ListResponse)
+	respLocales, err := ProtoToListPtrLocale(resp.Locales)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Locales: respLocales}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go b/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dca0bb8afa68dfe3fa17b53d315f3716c48ae36
--- /dev/null
+++ b/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,48 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/locales"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+)
+
+func PtrLocaleToProto(locale *service.Locale) (*pb.Locale, error) {
+	if locale == nil {
+		return nil, nil
+	}
+	return &pb.Locale{Id: locale.ID, Name: locale.Name, SpaceId: locale.SpaceID}, nil
+}
+
+func ProtoToPtrLocale(protoLocale *pb.Locale) (*service.Locale, error) {
+	if protoLocale == nil {
+		return nil, nil
+	}
+	return &service.Locale{ID: protoLocale.Id, Name: protoLocale.Name, SpaceID: protoLocale.SpaceId}, nil
+}
+
+func ListPtrLocaleToProto(locales []*service.Locale) ([]*pb.Locale, error) {
+	protoLocales := make([]*pb.Locale, 0, len(locales))
+	for _, l := range locales {
+		pl, err := PtrLocaleToProto(l)
+		if err != nil {
+			return nil, err
+		}
+		protoLocales = append(protoLocales, pl)
+	}
+	return protoLocales, nil
+}
+
+func ProtoToListPtrLocale(protoLocales []*pb.Locale) ([]*service.Locale, error) {
+	locales := make([]*service.Locale, 0, len(protoLocales))
+	for _, pl := range protoLocales {
+		l, err := ProtoToPtrLocale(pl)
+		if err != nil {
+			return nil, err
+		}
+		locales = append(locales, l)
+	}
+	return locales, nil
+}
diff --git a/pkg/locales/transport/grpc/server.microgen.go b/pkg/locales/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..88e549f6e23e99a04d037fcefb4603a13821d24a
--- /dev/null
+++ b/pkg/locales/transport/grpc/server.microgen.go
@@ -0,0 +1,67 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type localesServer struct {
+	create grpc.Handler
+	list   grpc.Handler
+	delete grpc.Handler
+
+	pb.UnimplementedLocalesServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.LocalesServer {
+	return &localesServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		list: grpc.NewServer(
+			endpoints.ListEndpoint,
+			_Decode_List_Request,
+			_Encode_List_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *localesServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+	_, resp, err := S.create.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CreateResponse), nil
+}
+
+func (S *localesServer) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
+	_, resp, err := S.list.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListResponse), nil
+}
+
+func (S *localesServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*empty.Empty, error) {
+	_, resp, err := S.delete.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/locales/transport/server.microgen.go b/pkg/locales/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ce815dcb52415a93ddfeb095f99e22fb14c4492
--- /dev/null
+++ b/pkg/locales/transport/server.microgen.go
@@ -0,0 +1,42 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc locales.Locales) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		ListEndpoint:   ListEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Locale)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func ListEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Locales: res0}, res1
+	}
+}
+
+func DeleteEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.LocaleId)
+		return &DeleteResponse{}, res0
+	}
+}
diff --git a/pkg/options/options.go b/pkg/options/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f8b0cd624db7e6c4c35fe62333a67ba633fbe7e
--- /dev/null
+++ b/pkg/options/options.go
@@ -0,0 +1,122 @@
+package options
+
+import "time"
+
+// SortOptions настройки сортировки результатов
+type SortOptions struct {
+	Sort []string
+}
+
+// PaginationOptions настройки возвращаемых страниц результатов
+type PaginationOptions struct {
+	PageNum  int
+	PageSize int
+}
+
+// FieldOptions настройки включения/исключения полей из результатов запроса
+type FieldOptions struct {
+	// Fields - Наименования полей для включения/исключения из результатов запроса (только указанные поля)
+	// Если `ExcludeFields` не установлен, то результат содержит только указанные поля
+	// Если `ExcludeFields` установлен, то результат содержит все поля кроме указанных
+	Fields []string
+
+	// ExcludeFields- Если флаг установлен, то перечисленные поля `Fields` следует исключить из результатов
+	ExcludeFields bool
+}
+
+// FindOptions настройки возвращаемых результатов поиска
+type FindOptions struct {
+	SortOptions
+	PaginationOptions
+	FieldOptions
+}
+
+// NewFindOptions создает новые результаты поиска
+func NewFindOptions(pageNum, pageSize int, sort ...string) *FindOptions {
+	return &FindOptions{
+		PaginationOptions: PaginationOptions{
+			PageNum:  pageNum,
+			PageSize: pageSize,
+		},
+		SortOptions: SortOptions{
+			Sort: sort,
+		},
+	}
+}
+
+// MergeFindOptions объединяет в FindOptions различные варианты настроек
+func MergeFindOptions(opts ...interface{}) *FindOptions {
+	fo := &FindOptions{}
+	for _, opt := range opts {
+		if opt == nil {
+			continue
+		}
+
+		switch o := opt.(type) {
+		case FindOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o.SortOptions)
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o.PaginationOptions)
+			fo.FieldOptions = MergeFieldOptions(fo.FieldOptions, o.FieldOptions)
+		case *FindOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o.SortOptions)
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o.PaginationOptions)
+			fo.FieldOptions = MergeFieldOptions(fo.FieldOptions, o.FieldOptions)
+		case SortOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o)
+		case *SortOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, *o)
+		case PaginationOptions:
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o)
+		case *PaginationOptions:
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, *o)
+		case FieldOptions:
+			fo.FieldOptions = o
+		case *FieldOptions:
+			fo.FieldOptions = *o
+		}
+	}
+	return fo
+}
+
+type TimeFilter struct {
+	Before, After time.Time
+}
+
+// MergeSortOptions объединяет настройки сортировки
+func MergeSortOptions(options ...SortOptions) SortOptions {
+	fo := SortOptions{}
+	for _, opt := range options {
+		if len(opt.Sort) == 0 {
+			continue
+		}
+		fo.Sort = append(fo.Sort, opt.Sort...)
+	}
+	return fo
+}
+
+// MergePaginationOptions объединяет настройки страниц
+func MergePaginationOptions(options ...PaginationOptions) PaginationOptions {
+	fo := PaginationOptions{}
+	for _, opt := range options {
+		if opt.PageSize == 0 && opt.PageNum == 0 {
+			continue
+		}
+		fo.PageNum = opt.PageNum
+		fo.PageSize = opt.PageSize
+	}
+	return fo
+}
+
+// MergeFieldOptions выполняет слияние опций для возвращаемых полей.
+// Выбирается не пустое значение.
+func MergeFieldOptions(options ...FieldOptions) FieldOptions {
+	fo := FieldOptions{}
+	for _, opt := range options {
+		if len(opt.Fields) > 0 {
+			fo.Fields = opt.Fields
+			fo.ExcludeFields = opt.ExcludeFields
+			return fo
+		}
+	}
+	return fo
+}
diff --git a/pkg/options/options_test.go b/pkg/options/options_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..981849a0e2625a8bd7e796a22eaa8529d606ef01
--- /dev/null
+++ b/pkg/options/options_test.go
@@ -0,0 +1,60 @@
+package options
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOptions_MergePaginationOptions(t *testing.T) {
+
+	var tt = []struct {
+		name     string
+		options  []PaginationOptions
+		expected PaginationOptions
+	}{
+		{
+			name:     "Nil option",
+			options:  nil,
+			expected: PaginationOptions{},
+		},
+		{
+			name:     "Empty options",
+			options:  []PaginationOptions{},
+			expected: PaginationOptions{},
+		},
+		{
+			name:     "One option",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #1",
+			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #2",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #3",
+			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #4",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+	}
+
+	for _, v := range tt {
+
+		t.Run(v.name, func(t *testing.T) {
+			actual := MergePaginationOptions(v.options...)
+			assert.Equal(t, v.expected, actual)
+		})
+	}
+}
diff --git a/pkg/users/mocks/Users.go b/pkg/users/mocks/Users.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e54f18c4dfc78d8473e5499262eb7d65c783e4d
--- /dev/null
+++ b/pkg/users/mocks/Users.go
@@ -0,0 +1,143 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Users is an autogenerated mock type for the Users type
+type Users struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, create
+func (_m *Users) Create(ctx context.Context, create *users.User) (*users.User, error) {
+	ret := _m.Called(ctx, create)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, *users.User) *users.User); ok {
+		r0 = rf(ctx, create)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *users.User) error); ok {
+		r1 = rf(ctx, create)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, userId
+func (_m *Users) Delete(ctx context.Context, userId string) error {
+	ret := _m.Called(ctx, userId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, userId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Find provides a mock function with given fields: ctx, filter, options
+func (_m *Users) Find(ctx context.Context, filter *users.Filter, opts *options.FindOptions) ([]*users.User, int, error) {
+	ret := _m.Called(ctx, filter, opts)
+
+	var r0 []*users.User
+	if rf, ok := ret.Get(0).(func(context.Context, *users.Filter, *options.FindOptions) []*users.User); ok {
+		r0 = rf(ctx, filter, opts)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*users.User)
+		}
+	}
+
+	var r1 int
+	if rf, ok := ret.Get(1).(func(context.Context, *users.Filter, *options.FindOptions) int); ok {
+		r1 = rf(ctx, filter, opts)
+	} else {
+		r1 = ret.Get(1).(int)
+	}
+
+	var r2 error
+	if rf, ok := ret.Get(2).(func(context.Context, *users.Filter, *options.FindOptions) error); ok {
+		r2 = rf(ctx, filter, opts)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// Get provides a mock function with given fields: ctx, userId
+func (_m *Users) Get(ctx context.Context, userId string) (*users.User, error) {
+	ret := _m.Called(ctx, userId)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, string) *users.User); ok {
+		r0 = rf(ctx, userId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, userId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetByIdentity provides a mock function with given fields: ctx, identity
+func (_m *Users) GetByIdentity(ctx context.Context, identity string) (*users.User, error) {
+	ret := _m.Called(ctx, identity)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, string) *users.User); ok {
+		r0 = rf(ctx, identity)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, identity)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: ctx, update
+func (_m *Users) Update(ctx context.Context, update *users.User) error {
+	ret := _m.Called(ctx, update)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *users.User) error); ok {
+		r0 = rf(ctx, update)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/users/service.go b/pkg/users/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d64a5ceceed51d3731eaa6641baf64409cc7389d
--- /dev/null
+++ b/pkg/users/service.go
@@ -0,0 +1,30 @@
+package users
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/users
+// @grpc-addr account.users.Users
+type Users interface {
+	Create(ctx context.Context, create *User) (user *User, err error)
+	Get(ctx context.Context, userId string) (user *User, err error)
+	Find(ctx context.Context, filter *Filter, options *options.FindOptions) (users []*User, total int, err error)
+	Update(ctx context.Context, update *User) (err error)
+	Delete(ctx context.Context, userId string) (err error)
+	GetByIdentity(ctx context.Context, identity string) (user *User, err error)
+}
+
+type Filter struct {
+	ID            []string
+	Name          []string
+	Identities    []string
+	DisplayName   []string
+	Email         []string
+	AvatarUri     []string
+	EmailVerified *bool
+	System        *bool
+}
diff --git a/pkg/users/transport/client.microgen.go b/pkg/users/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..74ca261a3bc5ec1cf99c655c82ec5b0345489ce3
--- /dev/null
+++ b/pkg/users/transport/client.microgen.go
@@ -0,0 +1,88 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *users.User) (res0 *users.User, res1 error) {
+	request := CreateRequest{Create: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).User, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string) (res0 *users.User, res1 error) {
+	request := GetRequest{UserId: arg1}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).User, res1
+}
+
+func (set EndpointsSet) Find(arg0 context.Context, arg1 *users.Filter, arg2 *options.FindOptions) (res0 []*users.User, res1 int, res2 error) {
+	request := FindRequest{
+		Filter:  arg1,
+		Options: arg2,
+	}
+	response, res2 := set.FindEndpoint(arg0, &request)
+	if res2 != nil {
+		if e, ok := status.FromError(res2); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res2 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*FindResponse).Users, response.(*FindResponse).Total, res2
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *users.User) (res0 error) {
+	request := UpdateRequest{Update: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string) (res0 error) {
+	request := DeleteRequest{UserId: arg1}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) GetByIdentity(arg0 context.Context, arg1 string) (res0 *users.User, res1 error) {
+	request := GetByIdentityRequest{Identity: arg1}
+	response, res1 := set.GetByIdentityEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetByIdentityResponse).User, res1
+}
diff --git a/pkg/users/transport/endpoints.microgen.go b/pkg/users/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..264025bfc25edd39b423f5ca983188bc6e3a9e60
--- /dev/null
+++ b/pkg/users/transport/endpoints.microgen.go
@@ -0,0 +1,15 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Users API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint        endpoint.Endpoint
+	GetEndpoint           endpoint.Endpoint
+	FindEndpoint          endpoint.Endpoint
+	UpdateEndpoint        endpoint.Endpoint
+	DeleteEndpoint        endpoint.Endpoint
+	GetByIdentityEndpoint endpoint.Endpoint
+}
diff --git a/pkg/users/transport/exchanges.microgen.go b/pkg/users/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..f70b8cdfe93eb8b843ec66136e8fa63bccc735ee
--- /dev/null
+++ b/pkg/users/transport/exchanges.microgen.go
@@ -0,0 +1,52 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+)
+
+type (
+	CreateRequest struct {
+		Create *users.User `json:"create"`
+	}
+	CreateResponse struct {
+		User *users.User `json:"user"`
+	}
+
+	GetRequest struct {
+		UserId string `json:"user_id"`
+	}
+	GetResponse struct {
+		User *users.User `json:"user"`
+	}
+
+	FindRequest struct {
+		Filter  *users.Filter        `json:"filter"`
+		Options *options.FindOptions `json:"options"`
+	}
+	FindResponse struct {
+		Users []*users.User `json:"users"`
+		Total int           `json:"total"`
+	}
+
+	UpdateRequest struct {
+		Update *users.User `json:"update"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		UserId string `json:"user_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	GetByIdentityRequest struct {
+		Identity string `json:"identity"`
+	}
+	GetByIdentityResponse struct {
+		User *users.User `json:"user"`
+	}
+)
diff --git a/pkg/users/transport/grpc/client.microgen.go b/pkg/users/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..98f4b74c1d23e667d8c1d2c7290cd136f5e20a5d
--- /dev/null
+++ b/pkg/users/transport/grpc/client.microgen.go
@@ -0,0 +1,61 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "account.users.Users"
+	}
+	return transport.EndpointsSet{
+		CreateEndpoint: grpckit.NewClient(
+			conn, addr, "Create",
+			_Encode_Create_Request,
+			_Decode_Create_Response,
+			pb.CreateResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteEndpoint: grpckit.NewClient(
+			conn, addr, "Delete",
+			_Encode_Delete_Request,
+			_Decode_Delete_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		FindEndpoint: grpckit.NewClient(
+			conn, addr, "Find",
+			_Encode_Find_Request,
+			_Decode_Find_Response,
+			pb.FindResponse{},
+			opts...,
+		).Endpoint(),
+		GetByIdentityEndpoint: grpckit.NewClient(
+			conn, addr, "GetByIdentity",
+			_Encode_GetByIdentity_Request,
+			_Decode_GetByIdentity_Response,
+			pb.GetByIdentityResponse{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..1837d41081572ab74ebfd7c9594a8292b4c9de29
--- /dev/null
+++ b/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,265 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	transport "git.perx.ru/perxis/perxis-go/pkg/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	reqCreate, err := PtrUserToProto(req.Create)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Create: reqCreate}, nil
+}
+
+func _Encode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*transport.GetRequest)
+	return &pb.GetRequest{UserId: req.UserId}, nil
+}
+
+func _Encode_Find_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil FindRequest")
+	}
+	req := request.(*transport.FindRequest)
+	reqFilter, err := PtrFilterToProto(req.Filter)
+	if err != nil {
+		return nil, err
+	}
+	reqOptions, err := PtrServicesFindOptionsToProto(req.Options)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindRequest{
+		Filter:  reqFilter,
+		Options: reqOptions,
+	}, nil
+}
+
+func _Encode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*transport.UpdateRequest)
+	reqUpdate, err := PtrUserToProto(req.Update)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Update: reqUpdate}, nil
+}
+
+func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*transport.DeleteRequest)
+	return &pb.DeleteRequest{UserId: req.UserId}, nil
+}
+
+func _Encode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*transport.CreateResponse)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{User: respUser}, nil
+}
+
+func _Encode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*transport.GetResponse)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{User: respUser}, nil
+}
+
+func _Encode_Find_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil FindResponse")
+	}
+	resp := response.(*transport.FindResponse)
+	respUsers, err := ListPtrUserToProto(resp.Users)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindResponse{
+		Total: int64(resp.Total),
+		Users: respUsers,
+	}, nil
+}
+
+func _Encode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*pb.CreateRequest)
+	reqCreate, err := ProtoToPtrUser(req.Create)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Create: reqCreate}, nil
+}
+
+func _Decode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*pb.GetRequest)
+	return &transport.GetRequest{UserId: string(req.UserId)}, nil
+}
+
+func _Decode_Find_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil FindRequest")
+	}
+	req := request.(*pb.FindRequest)
+	reqFilter, err := ProtoToPtrFilter(req.Filter)
+	if err != nil {
+		return nil, err
+	}
+	reqOptions, err := ProtoToPtrServicesFindOptions(req.Options)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindRequest{
+		Filter:  reqFilter,
+		Options: reqOptions,
+	}, nil
+}
+
+func _Decode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*pb.UpdateRequest)
+	reqUpdate, err := ProtoToPtrUser(req.Update)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Update: reqUpdate}, nil
+}
+
+func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*pb.DeleteRequest)
+	return &transport.DeleteRequest{UserId: string(req.UserId)}, nil
+}
+
+func _Decode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*pb.CreateResponse)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{User: respUser}, nil
+}
+
+func _Decode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*pb.GetResponse)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{User: respUser}, nil
+}
+
+func _Decode_Find_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil FindResponse")
+	}
+	resp := response.(*pb.FindResponse)
+	respUsers, err := ProtoToListPtrUser(resp.Users)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindResponse{
+		Total: int(resp.Total),
+		Users: respUsers,
+	}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_GetByIdentity_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByIdentityRequest")
+	}
+	req := request.(*transport.GetByIdentityRequest)
+	return &pb.GetByIdentityRequest{Identity: req.Identity}, nil
+}
+
+func _Encode_GetByIdentity_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByIdentityResponse")
+	}
+	resp := response.(*transport.GetByIdentityResponse)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetByIdentityResponse{User: respUser}, nil
+}
+
+func _Decode_GetByIdentity_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByIdentityRequest")
+	}
+	req := request.(*pb.GetByIdentityRequest)
+	return &transport.GetByIdentityRequest{Identity: string(req.Identity)}, nil
+}
+
+func _Decode_GetByIdentity_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByIdentityResponse")
+	}
+	resp := response.(*pb.GetByIdentityResponse)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetByIdentityResponse{User: respUser}, nil
+}
+
+func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/users/transport/grpc/protobuf_type_converters.microgen.go b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0789e52420b5383530e10f89e06100197a32bbb
--- /dev/null
+++ b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,153 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	service "git.perx.ru/perxis/perxis-go/pkg/users"
+	common "git.perx.ru/perxis/perxis-go/proto/common"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	"github.com/golang/protobuf/ptypes/wrappers"
+)
+
+func PtrUserToProto(create *service.User) (*pb.User, error) {
+	if create == nil {
+		return nil, nil
+	}
+	u := &pb.User{
+		Id:          create.ID,
+		Name:        create.Name,
+		Identities:  create.Identities,
+		DisplayName: create.DisplayName,
+		Email:       create.Email,
+		AvatarUrl:   create.AvatarURL,
+	}
+	if create.EmailVerified != nil {
+		u.EmailVerified = &wrappers.BoolValue{
+			Value: *create.EmailVerified,
+		}
+	}
+	if create.System != nil {
+		u.System = &wrappers.BoolValue{
+			Value: *create.System,
+		}
+	}
+	return u, nil
+}
+
+func ProtoToPtrUser(protoCreate *pb.User) (*service.User, error) {
+	if protoCreate == nil {
+		return nil, nil
+	}
+	user := &service.User{
+		ID:          protoCreate.Id,
+		Name:        protoCreate.Name,
+		DisplayName: protoCreate.DisplayName,
+		Identities:  protoCreate.Identities,
+		Email:       protoCreate.Email,
+		AvatarURL:   protoCreate.AvatarUrl,
+	}
+	if protoCreate.EmailVerified != nil {
+		user.EmailVerified = &protoCreate.EmailVerified.Value
+	}
+	if protoCreate.System != nil {
+		user.System = &protoCreate.System.Value
+	}
+	return user, nil
+}
+
+func PtrFilterToProto(filter *service.Filter) (*pb.Filter, error) {
+	if filter == nil {
+		return nil, nil
+	}
+	f := &pb.Filter{
+		Id:          filter.ID,
+		Name:        filter.Name,
+		Identities:  filter.Identities,
+		DisplayName: filter.DisplayName,
+		Email:       filter.Email,
+	}
+	if filter.EmailVerified != nil {
+		f.EmailVerified = &wrappers.BoolValue{
+			Value: *filter.EmailVerified,
+		}
+	}
+	if filter.System != nil {
+		f.System = &wrappers.BoolValue{
+			Value: *filter.System,
+		}
+	}
+	return f, nil
+}
+
+func ProtoToPtrFilter(protoFilter *pb.Filter) (*service.Filter, error) {
+	if protoFilter == nil {
+		return nil, nil
+	}
+	f := &service.Filter{
+		ID:          protoFilter.Id,
+		Name:        protoFilter.Name,
+		Identities:  protoFilter.Identities,
+		DisplayName: protoFilter.DisplayName,
+		Email:       protoFilter.Email,
+	}
+	if protoFilter.EmailVerified != nil {
+		f.EmailVerified = &protoFilter.EmailVerified.Value
+	}
+	if protoFilter.System != nil {
+		f.System = &protoFilter.System.Value
+	}
+	return f, nil
+}
+
+func ListPtrUserToProto(users []*service.User) ([]*pb.User, error) {
+	protoUsers := make([]*pb.User, 0, len(users))
+	for _, u := range users {
+		pu, err := PtrUserToProto(u)
+		if err != nil {
+			return nil, err
+		}
+		protoUsers = append(protoUsers, pu)
+	}
+	return protoUsers, nil
+}
+
+func ProtoToListPtrUser(protoCreates []*pb.User) ([]*service.User, error) {
+	users := make([]*service.User, 0, len(protoCreates))
+	for _, pu := range protoCreates {
+		u, err := ProtoToPtrUser(pu)
+		if err != nil {
+			return nil, err
+		}
+		users = append(users, u)
+	}
+	return users, nil
+}
+
+func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*common.FindOptions, error) {
+	if opts == nil {
+		return nil, nil
+	}
+	return &common.FindOptions{
+		Sort:     opts.Sort,
+		PageNum:  int32(opts.PageNum),
+		PageSize: int32(opts.PageSize),
+	}, nil
+}
+
+func ProtoToPtrServicesFindOptions(protoOpts *common.FindOptions) (*options.FindOptions, error) {
+	if protoOpts == nil {
+		return nil, nil
+	}
+	return &options.FindOptions{
+		SortOptions: options.SortOptions{
+			Sort: protoOpts.Sort,
+		},
+		PaginationOptions: options.PaginationOptions{
+			PageNum:  int(protoOpts.PageNum),
+			PageSize: int(protoOpts.PageSize),
+		},
+	}, nil
+}
diff --git a/pkg/users/transport/grpc/server.microgen.go b/pkg/users/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..2be01e97a5a98fcf5189b386fbb36d1a60f43db7
--- /dev/null
+++ b/pkg/users/transport/grpc/server.microgen.go
@@ -0,0 +1,112 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type usersServer struct {
+	create        grpc.Handler
+	get           grpc.Handler
+	find          grpc.Handler
+	update        grpc.Handler
+	delete        grpc.Handler
+	getByIdentity grpc.Handler
+
+	pb.UnimplementedUsersServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.UsersServer {
+	return &usersServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		find: grpc.NewServer(
+			endpoints.FindEndpoint,
+			_Decode_Find_Request,
+			_Encode_Find_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		getByIdentity: grpc.NewServer(
+			endpoints.GetByIdentityEndpoint,
+			_Decode_GetByIdentity_Request,
+			_Encode_GetByIdentity_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *usersServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+	_, resp, err := S.create.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CreateResponse), nil
+}
+
+func (S *usersServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	_, resp, err := S.get.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetResponse), nil
+}
+
+func (S *usersServer) Find(ctx context.Context, req *pb.FindRequest) (*pb.FindResponse, error) {
+	_, resp, err := S.find.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.FindResponse), nil
+}
+
+func (S *usersServer) Update(ctx context.Context, req *pb.UpdateRequest) (*empty.Empty, error) {
+	_, resp, err := S.update.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *usersServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*empty.Empty, error) {
+	_, resp, err := S.delete.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *usersServer) GetByIdentity(ctx context.Context, req *pb.GetByIdentityRequest) (*pb.GetByIdentityResponse, error) {
+	_, resp, err := S.getByIdentity.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetByIdentityResponse), nil
+}
diff --git a/pkg/users/transport/server.microgen.go b/pkg/users/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..e12645efc79921da957e359848d9dca606f6364b
--- /dev/null
+++ b/pkg/users/transport/server.microgen.go
@@ -0,0 +1,72 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc users.Users) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint:        CreateEndpoint(svc),
+		DeleteEndpoint:        DeleteEndpoint(svc),
+		FindEndpoint:          FindEndpoint(svc),
+		GetByIdentityEndpoint: GetByIdentityEndpoint(svc),
+		GetEndpoint:           GetEndpoint(svc),
+		UpdateEndpoint:        UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Create)
+		return &CreateResponse{User: res0}, res1
+	}
+}
+
+func GetEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.UserId)
+		return &GetResponse{User: res0}, res1
+	}
+}
+
+func FindEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*FindRequest)
+		res0, res1, res2 := svc.Find(arg0, req.Filter, req.Options)
+		return &FindResponse{
+			Total: res1,
+			Users: res0,
+		}, res2
+	}
+}
+
+func UpdateEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Update)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.UserId)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func GetByIdentityEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetByIdentityRequest)
+		res0, res1 := svc.GetByIdentity(arg0, req.Identity)
+		return &GetByIdentityResponse{User: res0}, res1
+	}
+}
diff --git a/pkg/users/user.go b/pkg/users/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..9eca6efc24569c2ef951d2bcb059440d54896ecc
--- /dev/null
+++ b/pkg/users/user.go
@@ -0,0 +1,31 @@
+package users
+
+// Current - Идентификатор, который можно использовать для получения/обновления/регистрации
+// пользователя, от имени которого был сделан запрос.
+const Current = "current"
+
+type User struct {
+	ID            string   `json:"id" bson:"_id"`
+	Name          string   `json:"name" bson:"name"`
+	DisplayName   string   `json:"displayName" bson:"displayName"`
+	Identities    []string `json:"identities" bson:"identities"`
+	Email         string   `json:"email" bson:"email"`
+	EmailVerified *bool    `json:"emailVerified" bson:"emailVerified"`
+	AvatarURL     string   `json:"avatarUrl" bson:"avatarUrl,omitempty"`
+	System        *bool    `json:"system" bson:"system"`
+}
+
+func (u User) GetID() string {
+	return u.ID
+}
+
+func (u User) IsSystem() bool {
+	if u.System != nil {
+		return *u.System
+	}
+	return false
+}
+
+func (u User) Clone() *User {
+	return &u
+}