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
+}