diff --git a/id/bson.go b/id/bson.go
deleted file mode 100644
index 9d44c5a8074361b381524dd7c99ad77906cfc68c..0000000000000000000000000000000000000000
--- a/id/bson.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package id
-
-import (
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-	"go.mongodb.org/mongo-driver/bson"
-	"go.mongodb.org/mongo-driver/bson/bsonrw"
-	"go.mongodb.org/mongo-driver/bson/bsontype"
-)
-
-func (id *ID) MarshalBSONValue() (bsontype.Type, []byte, error) {
-	return bson.MarshalValue(id.String())
-}
-
-func (id *ID) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
-	if btype != bson.TypeString {
-		return errors.New("cannot unmarshal non-string bson value to ID")
-	}
-	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
-	if err != nil {
-		return err
-	}
-	var str string
-	if err = dec.Decode(&str); err != nil {
-		return err
-	}
-	t, err := Parse(str)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/bson_test.go b/id/bson_test.go
index c8080b5d7ae12181343a8f6502e76484c414e578..7ae62ccba4e1dbc2e74ac48b8c5890216a6cba50 100644
--- a/id/bson_test.go
+++ b/id/bson_test.go
@@ -11,64 +11,64 @@ import (
 func TestID_MarshalUnmarshalBSON(t *testing.T) {
 	tests := []struct {
 		name string
-		id   *ID
+		id   *ObjectId
 	}{
 		{
 			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}},
 		},
 		{
 			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+			id:   &ObjectId{Descriptor: &UserId{UserID: "1"}},
 		},
 		{
 			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}},
 		},
 		{
 			name: "EnvironmentID",
-			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "ClientID",
-			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "RoleID",
-			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &RoleId{RoleID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ObjectId{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ObjectId{Descriptor: &SchemaId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ObjectId{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ObjectId{Descriptor: &RevisionId{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
+			id:   &ObjectId{Descriptor: &SystemId{}},
 		},
 	}
 	type test struct {
 		Text string
-		ID   *ID
+		ID   *ObjectId
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -86,13 +86,14 @@ func TestID_MarshalUnmarshalBSON(t *testing.T) {
 
 func TestID_ExampleBSON(t *testing.T) {
 	type data struct {
-		ID     *ID
+		ID *ObjectId
+
 		Text   string
 		Number int
 	}
 
 	test := &data{
-		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		ID:     &ObjectId{Descriptor: &SpaceId{SpaceID: Space}},
 		Text:   "text",
 		Number: 1,
 	}
diff --git a/id/client.go b/id/client.go
index db941c5ad54b539551193004674ac15f15d314be..704f8d5f27f756f1f65d73ab138f08cc090bdf7d 100644
--- a/id/client.go
+++ b/id/client.go
@@ -1,61 +1,67 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+)
+
 const (
 	Client        = "client"
 	ClientsPrefix = "clients"
 )
 
-type ClientID struct {
-	SpaceID
+var _ Descriptor = &ClientId{}
+
+type ClientId struct {
+	SpaceId
 	ClientID string `json:"client_id,omitempty" bson:"client_id,omitempty"`
 }
 
-func (t *ClientID) Type() string { return Client }
-
-func (t *ClientID) String() string {
-	return Join(t.SpaceID.String(), ClientsPrefix, t.ClientID)
-
+func (id *ClientId) New() Descriptor {
+	return &ClientId{}
 }
 
-func (t *ClientID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["client_id"] = t.ClientID
-	m["type"] = Client
-	return m
+func (id *ClientId) Type() string { return Client }
+
+func (id *ClientId) String() string {
+	return Join(id.SpaceId.String(), ClientsPrefix, id.ClientID)
 }
 
-func (t *ClientID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *ClientId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != ClientsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.ClientID = m["client_id"].(string)
+	id.ClientID = parts[3]
 	return nil
 }
 
-func (t *ClientID) Validate() error {
-	if t.ClientID == "" {
-		return ErrInvalidID
-	}
-
-	return t.SpaceID.Validate()
+func (id *ClientId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["client_id"] = id.ClientID
+	m["type"] = Client
+	return m
 }
 
-func parseClientID(parts []string) (*ClientID, error) {
-	if len(parts) != 4 || parts[2] != ClientsPrefix {
-		return nil, ErrInvalidID
+func (id *ClientId) FromMap(m map[string]any) error {
+	id.ClientID, _ = m["client_id"].(string)
+	if id.ClientID == "" {
+		return fmt.Errorf("%w: ClientID required", ErrInvalidID)
 	}
+	return id.SpaceId.FromMap(m)
+}
 
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *ClientId) Validate() error {
+	if id.ClientID == "" {
+		return fmt.Errorf("%w: ClientID required", ErrInvalidID)
 	}
 
-	var id ClientID
-	id.SpaceID = *spaceID
-	id.ClientID = parts[3]
-	return &id, nil
+	return id.SpaceId.Validate()
 }
 
-func NewClientID(spaceID, id string) *ID {
-	return &ID{Descriptor: &ClientID{SpaceID: SpaceID{SpaceID: spaceID}, ClientID: id}}
+func NewClientId(c clients.Client) *ObjectId {
+	return &ObjectId{Descriptor: &ClientId{SpaceId: SpaceId{SpaceID: c.SpaceID}, ClientID: c.ID}}
 }
diff --git a/id/collection.go b/id/collection.go
index e0f37585b41e77b58530cc50f249283beb6d8eca..6518513b59238f0377aafe495dd4cac1f43a8436 100644
--- a/id/collection.go
+++ b/id/collection.go
@@ -1,60 +1,74 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+)
+
 const (
 	Collection        = "collection"
 	CollectionsPrefix = "cols"
 )
 
-type CollectionID struct {
-	EnvironmentID
+var _ Descriptor = &CollectionId{}
+
+type CollectionId struct {
+	EnvironmentId
 	CollectionID string `json:"col_id,omitempty" bson:"col_id, omitempty"`
 }
 
-func (t *CollectionID) Type() string { return Collection }
+func (id *CollectionId) New() Descriptor {
+	return &CollectionId{EnvironmentId: EnvironmentId{SpaceId: SpaceId{}}}
+}
 
-func (t *CollectionID) String() string {
-	return Join(t.EnvironmentID.String(), CollectionsPrefix, t.CollectionID)
+func (id *CollectionId) Type() string { return Collection }
+
+func (id *CollectionId) String() string {
+	return Join(id.EnvironmentId.String(), CollectionsPrefix, id.CollectionID)
 }
 
-func (t *CollectionID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
-	m["col_id"] = t.CollectionID
+func (id *CollectionId) Map() map[string]any {
+	m := id.EnvironmentId.Map()
+	m["col_id"] = id.CollectionID
 	m["type"] = Collection
 	return m
 }
 
-func (t *CollectionID) FromMap(m map[string]any) error {
-	if err := t.EnvironmentID.FromMap(m); err != nil {
+func (id *CollectionId) FromParts(parts []string) error {
+	if len(parts) != 6 || parts[4] != CollectionsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.EnvironmentId.FromParts(parts[:4]); err != nil {
 		return err
 	}
-	t.CollectionID = m["col_id"].(string)
+	id.CollectionID = parts[5]
 	return nil
 }
 
-func (t *CollectionID) Validate() error {
-	if t.CollectionID == "" {
-		return ErrInvalidID
+func (id *CollectionId) FromMap(m map[string]any) error {
+	id.CollectionID, _ = m["col_id"].(string)
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalidID)
 	}
-
-	return t.EnvironmentID.Validate()
+	return id.EnvironmentId.FromMap(m)
 }
 
-func parseCollectionID(parts []string) (*CollectionID, error) {
-	if len(parts) != 6 || parts[4] != CollectionsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	envID, err := parseEnvironmentID(parts[:4])
-	if err != nil {
-		return nil, err
+func (id *CollectionId) Validate() error {
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalidID)
 	}
-
-	var id CollectionID
-	id.CollectionID = parts[5]
-	id.EnvironmentID = *envID
-	return &id, nil
+	return id.EnvironmentId.Validate()
 }
 
-func NewCollectionID(spaceID, envID, id string) *ID {
-	return &ID{Descriptor: &CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+func NewCollectionId(coll collections.Collection) *ObjectId {
+	return &ObjectId{Descriptor: &CollectionId{
+		EnvironmentId: EnvironmentId{
+			SpaceId: SpaceId{
+				SpaceID: coll.SpaceID,
+			},
+			EnvironmentID: coll.EnvID,
+		},
+		CollectionID: coll.ID,
+	}}
 }
diff --git a/id/environment.go b/id/environment.go
index d42df3e658bc04ae70989d199257a7836bd30f00..0da72195ae0e1bc9d9262e6b958f61c30c0a2f36 100644
--- a/id/environment.go
+++ b/id/environment.go
@@ -1,61 +1,71 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+)
+
 const (
 	Environment        = "environment"
 	EnvironmentsPrefix = "envs"
 )
 
-type EnvironmentID struct {
-	SpaceID
+var _ Descriptor = &EnvironmentId{}
+
+type EnvironmentId struct {
+	SpaceId
 	EnvironmentID string `json:"env_id,omitempty" bson:"env_id,omitempty"`
 }
 
-func (t *EnvironmentID) Type() string { return Environment }
+func (id *EnvironmentId) New() Descriptor {
+	return &EnvironmentId{}
+}
 
-func (t *EnvironmentID) String() string {
-	return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID)
+func (id *EnvironmentId) Type() string { return "environment" }
+
+func (id *EnvironmentId) String() string {
+	return Join(id.SpaceId.String(), EnvironmentsPrefix, id.EnvironmentID)
 
 }
 
-func (t *EnvironmentID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["env_id"] = t.EnvironmentID
+func (id *EnvironmentId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["env_id"] = id.EnvironmentID
 	m["type"] = Environment
 	return m
 }
 
-func (t *EnvironmentID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *EnvironmentId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.EnvironmentID = m["env_id"].(string)
+	id.EnvironmentID = parts[3]
 	return nil
 }
 
-func (t *EnvironmentID) Validate() error {
-	if t.EnvironmentID == "" {
-		return ErrInvalidID
+func (id *EnvironmentId) FromMap(m map[string]any) error {
+	id.EnvironmentID, _ = m["env_id"].(string)
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnviromentID required", ErrInvalidID)
 	}
-
-	return t.SpaceID.Validate()
+	return id.SpaceId.FromMap(m)
 }
 
-func parseEnvironmentID(parts []string) (*EnvironmentID, error) {
-	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
-		return nil, ErrInvalidID
+func (id *EnvironmentId) Validate() error {
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnvironmetnID required", ErrInvalidID)
 	}
-
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
-	}
-
-	var id EnvironmentID
-	id.EnvironmentID = parts[3]
-	id.SpaceID = *spaceID
-	return &id, nil
+	return id.SpaceId.Validate()
 }
-
-func NewEnvironmentID(spaceID, id string) *ID {
-	return &ID{Descriptor: &EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: id}}
+func NewEnvironmentId(env environments.Environment) *ObjectId {
+	return &ObjectId{Descriptor: &EnvironmentId{
+		SpaceId: SpaceId{
+			SpaceID: env.SpaceID,
+		},
+		EnvironmentID: env.ID,
+	}}
 }
diff --git a/id/error.go b/id/error.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8332dc96a651e337cdc34ec83d06442ff27048e
--- /dev/null
+++ b/id/error.go
@@ -0,0 +1,11 @@
+package id
+
+import "errors"
+
+var (
+	ErrInvalidID       = errors.New("invalid ObjectId")
+	ErrUnknownType     = errors.New("unknown ObjectId type")
+	ErrHandlerNotFound = errors.New("ObjectId handler not found")
+	ErrTypeNotFound    = errors.New("type key not found in map")
+	ErrInvalidType     = errors.New("ObjectId should be a string")
+)
diff --git a/id/field.go b/id/field.go
index ca1577552dc7df38d555549ddfc53a51e70e63f1..024732f2d44d7a2ac4db63418ba113563a379c8d 100644
--- a/id/field.go
+++ b/id/field.go
@@ -1,60 +1,82 @@
 package id
 
+import "fmt"
+
 const (
 	Field        = "field"
 	FieldsPrefix = "fields"
 )
 
-type FieldID struct {
-	ItemID
-	FieldName string `json:"field_name,omitempty" bson:"field_name,omitempty"`
+type FieldId struct {
+	ItemId
+	Field string `json:"field,omitempty" bson:"field,omitempty"`
 }
 
-func (t *FieldID) Type() string { return Field }
-
-func (t *FieldID) String() string {
-	return Join(t.ItemID.String(), FieldsPrefix, t.FieldName)
+var _ Descriptor = &FieldId{}
 
+func (id *FieldId) New() Descriptor {
+	return &FieldId{}
 }
 
-func (t *FieldID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
-	m["field_name"] = t.FieldName
-	m["type"] = Field
-	return m
+func (id *FieldId) Type() string { return Field }
+
+func (id *FieldId) String() string {
+	return Join(id.ItemId.String(), FieldsPrefix, id.Field)
+
 }
 
-func (t *FieldID) FromMap(m map[string]any) error {
-	if err := t.ItemID.FromMap(m); err != nil {
+func (id *FieldId) FromParts(parts []string) error {
+	if len(parts) != 10 || parts[8] != FieldsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.ItemId.FromParts(parts[:8]); err != nil {
 		return err
 	}
-	t.FieldName = m["field_name"].(string)
+	id.Field = parts[9]
 	return nil
 }
 
-func (t *FieldID) Validate() error {
-	if t.FieldName == "" {
-		return ErrInvalidID
-	}
-
-	return t.ItemID.Validate()
+func (id *FieldId) Map() map[string]any {
+	m := id.ItemId.Map()
+	m["field"] = id.Field
+	m["type"] = Field
+	return m
 }
 
-func parseFieldID(parts []string) (*FieldID, error) {
-	if len(parts) != 10 || parts[8] != FieldsPrefix {
-		return nil, ErrInvalidID
+func (id *FieldId) FromMap(m map[string]any) error {
+	id.Field = m["field"].(string)
+	if id.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalidID)
+	}
+
+	if err := id.ItemId.FromMap(m); err != nil {
+		return err
 	}
+	return nil
+}
 
-	itemID, err := parseItemID(parts[:8])
-	if err != nil {
-		return nil, err
+func (id *FieldId) Validate() error {
+	if id.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalidID)
 	}
 
-	var id FieldID
-	id.ItemID = *itemID
-	id.FieldName = parts[9]
-	return &id, nil
+	return id.ItemId.Validate()
 }
-func NewFieldID(spaceID, envID, collID, itemID, id string) *ID {
-	return &ID{Descriptor: &FieldID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, FieldName: id}}
+
+func NewFieldId(spaceID, envID, collID, itemID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &FieldId{
+		ItemId: ItemId{
+			CollectionId: CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId: SpaceId{
+						SpaceID: spaceID,
+					},
+					EnvironmentID: envID,
+				},
+				CollectionID: collID,
+			},
+			ItemID: itemID,
+		},
+		Field: id,
+	}}
 }
diff --git a/id/id.go b/id/id.go
deleted file mode 100644
index ca75c29f94b6fe9100b435e8f546835991bc9fa3..0000000000000000000000000000000000000000
--- a/id/id.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package id
-
-import (
-	"strings"
-
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-)
-
-const Separator = '/'
-
-var (
-	ErrInvalidID = errors.New("invalid id")
-)
-
-type Descriptor interface {
-	String() string
-	Type() string
-	ToMap() map[string]any
-	FromMap(map[string]any) error
-	Validate() error
-}
-
-type ID struct {
-	Descriptor
-}
-
-func Parse(s string) (*ID, error) {
-	parts := Split(s)
-
-	if id, _ := parseServiceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseUserID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseOrganizationID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSpaceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseEnvironmentID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseClientID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRoleID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseCollectionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSchemaID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseItemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRevisionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseFieldID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSystemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	return nil, ErrInvalidID
-}
-
-func Split(id string) []string {
-	if id[0] != Separator {
-		return nil
-	}
-	return strings.Split(id[1:], string(Separator))
-}
-
-func Join(parts ...string) string {
-	s := strings.Join(parts, string(Separator))
-	if s[0] != Separator {
-		s = string(Separator) + s
-	}
-	return s
-}
-
-func FromMap(m map[string]any) (*ID, error) {
-	if m == nil {
-		return nil, errors.New("nil map")
-	}
-
-	v := new(ID)
-
-	switch m["type"] {
-	case Organization:
-		v.Descriptor = new(OrganizationID)
-	case Service:
-		v.Descriptor = new(ServiceID)
-	case User:
-		v.Descriptor = new(UserID)
-	case Space:
-		v.Descriptor = new(SpaceID)
-	case Environment:
-		v.Descriptor = new(EnvironmentID)
-	case Client:
-		v.Descriptor = new(ClientID)
-	case Role:
-		v.Descriptor = new(RoleID)
-	case Collection:
-		v.Descriptor = new(CollectionID)
-	case Schema:
-		v.Descriptor = new(SchemaID)
-	case Item:
-		v.Descriptor = new(ItemID)
-	case Revision:
-		v.Descriptor = new(RevisionID)
-	case Field:
-		v.Descriptor = new(FieldID)
-	case System:
-		v.Descriptor = new(SystemID)
-	default:
-		return nil, errors.New("unknown type")
-	}
-
-	if err := v.Descriptor.FromMap(m); err != nil {
-		return nil, err
-	}
-
-	return v, nil
-}
diff --git a/id/id_test.go b/id/id_test.go
deleted file mode 100644
index 041f1cdc6018e6c84c0c4c6a4be4b3944ef34082..0000000000000000000000000000000000000000
--- a/id/id_test.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package id
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func Test_ParseID(t *testing.T) {
-	tests := []struct {
-		name      string
-		id        string
-		result    *ID
-		wantError bool
-	}{
-		{
-			name:   "ServiceID",
-			id:     "/services/<service_id>",
-			result: &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name:   "UserID",
-			id:     "/users/<user_id>",
-			result: &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name:   "OrganizationID",
-			id:     "/orgs/<org_id>",
-			result: &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name:   "SpaceID",
-			id:     "/spaces/<space_id>",
-			result: &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id:   "/spaces/<space_id>/clients/<client_id>",
-			result: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id:   "/spaces/<space_id>/roles/<role_id>",
-			result: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id:   "/spaces/<space_id>/envs/<env_id>",
-			result: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
-			result: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "SchemaID",
-			id:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
-			result: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
-			result: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
-			result: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
-			result: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name:   "SystemID",
-			id:     "/system",
-			result: &ID{Descriptor: &SystemID{}},
-		},
-		{
-			name:      "With error #1: no backslash in the beginning of id",
-			id:        "spaces/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #2: backslash in the end of id",
-			id:        "/spaces/<space_id>/",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #3: typo in 'spaces'",
-			id:        "/space/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #4: no space_id in id",
-			id:        "/spaces",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #5: multiple backslashes in the end of id",
-			id:        "/spaces/<space_id>///",
-			result:    nil,
-			wantError: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			id, err := Parse(tt.id)
-			if tt.wantError {
-				require.Error(t, err)
-				return
-			}
-			require.NoError(t, err)
-			require.Equal(t, tt.result, id)
-			require.Equal(t, tt.id, id.String(), "проверяем корректность работы метода String, полученное ID должно совпадать с исходным")
-		})
-	}
-}
-
-func Test_Map(t *testing.T) {
-	tests := []struct {
-		name string
-		id   *ID
-	}{
-		{
-			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "Schema ID",
-			id: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			v, err := FromMap(tt.id.ToMap())
-			require.NoError(t, err)
-			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
-			assert.Equal(t, v.ToMap(), tt.id.ToMap())
-		})
-	}
-}
diff --git a/id/item.go b/id/item.go
index 70c3e7be95b517a85e07e487eb00f94f9f73e14f..dbfb8e18cb5128fc0ab26a4298203a38a4af1ad6 100644
--- a/id/item.go
+++ b/id/item.go
@@ -1,61 +1,80 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+)
+
 const (
 	Item        = "item"
 	ItemsPrefix = "items"
 )
 
-type ItemID struct {
-	CollectionID
+type ItemId struct {
+	CollectionId
 	ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"`
 }
 
-func (t *ItemID) Type() string { return Item }
+var _ Descriptor = &ItemId{}
+
+func (i *ItemId) New() Descriptor {
+	return &ItemId{}
+}
 
-func (t *ItemID) String() string {
-	return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID)
+func (i *ItemId) Type() string { return Item }
+
+func (i *ItemId) String() string {
+	return Join(i.CollectionId.String(), ItemsPrefix, i.ItemID)
 
 }
 
-func (t *ItemID) ToMap() map[string]any {
-	m := t.CollectionID.ToMap()
-	m["item_id"] = t.ItemID
+func (i *ItemId) Map() map[string]any {
+	m := i.CollectionId.Map()
+	m["item_id"] = i.ItemID
 	m["type"] = Item
 	return m
 }
 
-func (t *ItemID) FromMap(m map[string]any) error {
-	if err := t.CollectionID.FromMap(m); err != nil {
+func (i *ItemId) FromParts(parts []string) error {
+	if len(parts) != 8 || parts[6] != ItemsPrefix {
+		return ErrInvalidID
+	}
+
+	if err := i.CollectionId.FromParts(parts[:6]); err != nil {
 		return err
 	}
-	t.ItemID = m["item_id"].(string)
+
+	i.ItemID = parts[7]
 	return nil
 }
 
-func (t *ItemID) Validate() error {
-	if t.ItemID == "" {
-		return ErrInvalidID
+func (i *ItemId) FromMap(m map[string]any) error {
+	i.ItemID = m["item_id"].(string)
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalidID)
 	}
 
-	return t.CollectionID.Validate()
+	return i.CollectionId.FromMap(m)
 }
 
-func parseItemID(parts []string) (*ItemID, error) {
-	if len(parts) != 8 || parts[6] != ItemsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	collID, err := parseCollectionID(parts[:6])
-	if err != nil {
-		return nil, err
+func (i *ItemId) Validate() error {
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalidID)
 	}
-
-	var id ItemID
-	id.CollectionID = *collID
-	id.ItemID = parts[7]
-	return &id, nil
+	return i.CollectionId.Validate()
 }
-
-func NewItemID(spaceID, envID, collID, id string) *ID {
-	return &ID{Descriptor: &ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: id}}
+func NewItemId(i items.Item) *ObjectId {
+	return &ObjectId{Descriptor: &ItemId{
+		CollectionId: CollectionId{
+			EnvironmentId: EnvironmentId{
+				SpaceId: SpaceId{
+					SpaceID: i.SpaceID,
+				},
+				EnvironmentID: i.EnvID,
+			},
+			CollectionID: i.CollectionID,
+		},
+		ItemID: i.ID,
+	}}
 }
diff --git a/id/json.go b/id/json.go
deleted file mode 100644
index 34b87589dfd81e6b793c5812fb2b90f43fb724f1..0000000000000000000000000000000000000000
--- a/id/json.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package id
-
-import (
-	jsoniter "github.com/json-iterator/go"
-)
-
-func (id *ID) MarshalJSON() ([]byte, error) {
-	return jsoniter.Marshal(id.String())
-}
-
-func (id *ID) UnmarshalJSON(b []byte) error {
-	var s string
-	var err error
-	if err = jsoniter.Unmarshal(b, &s); err != nil {
-		return err
-	}
-	t, err := Parse(s)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/json_test.go b/id/json_test.go
index afe831d4ef6038eaa6bc8a565b0623334cdf6bc8..0da4f292db94ffc93558b76ba702f63b0ce5a121 100644
--- a/id/json_test.go
+++ b/id/json_test.go
@@ -11,59 +11,59 @@ import (
 func TestID_MarshalUnmarshalJSON(t *testing.T) {
 	tests := []struct {
 		name string
-		id   *ID
+		id   *ObjectId
 	}{
 		{
 			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}},
 		},
 		{
 			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+			id:   &ObjectId{Descriptor: &UserId{UserID: "1"}},
 		},
 		{
 			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}},
 		},
 		{
 			name: "EnvironmentID",
-			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "ClientID",
-			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
 			name: "RoleID",
-			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+			id:   &ObjectId{Descriptor: &RoleId{RoleID: "1", SpaceId: SpaceId{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ObjectId{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ObjectId{Descriptor: &SchemaId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ObjectId{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ObjectId{Descriptor: &RevisionId{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
+			id:   &ObjectId{Descriptor: &SystemId{}},
 		},
 	}
 	for _, tt := range tests {
@@ -71,7 +71,7 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 			b, err := jsoniter.Marshal(&tt.id)
 			require.NoError(t, err)
 
-			var i ID
+			var i ObjectId
 			require.NoError(t, jsoniter.Unmarshal(b, &i))
 			assert.Equal(t, tt.id, &i, "после Unmarshal объект должен быть идентичен исходному")
 		})
@@ -80,13 +80,13 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 
 func TestID_ExampleJSON(t *testing.T) {
 	type data struct {
-		ID     *ID
+		ID     *ObjectId
 		Text   string
 		Number int
 	}
 
 	test := &data{
-		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		ID:     &ObjectId{Descriptor: &SpaceId{SpaceID: Space}},
 		Text:   "text",
 		Number: 1,
 	}
diff --git a/id/object_id.go b/id/object_id.go
new file mode 100644
index 0000000000000000000000000000000000000000..e84ea1c0fff9960bfef7584fbd68699509cfa2a6
--- /dev/null
+++ b/id/object_id.go
@@ -0,0 +1,102 @@
+package id
+
+import (
+	jsoniter "github.com/json-iterator/go"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"strings"
+)
+
+const Separator = '/'
+
+type Descriptor interface {
+	New() Descriptor
+	String() string
+	Type() string
+	Map() map[string]any
+	Validate() error
+	FromMap(map[string]any) error
+	FromParts([]string) error
+}
+
+type ObjectId struct {
+	Descriptor
+}
+
+func NewObjectId(in any) (*ObjectId, error) {
+	switch v := in.(type) {
+	case Descriptor:
+		return &ObjectId{Descriptor: v}, nil
+	case string:
+		return FromString(v)
+	case map[string]any:
+		return FromMap(v)
+	}
+	return FromObject(in)
+}
+
+func MustObjectId(in any) *ObjectId {
+	oid, err := NewObjectId(in)
+	if err != nil {
+		panic(err)
+	}
+	return oid
+}
+
+func (oid *ObjectId) MarshalJSON() ([]byte, error) {
+	return jsoniter.Marshal(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalJSON(b []byte) error {
+	var s string
+	var err error
+	if err = jsoniter.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	t, err := FromString(s)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func (oid *ObjectId) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return bson.MarshalValue(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
+	if btype != bson.TypeString {
+		return ErrInvalidType
+	}
+	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
+	if err != nil {
+		return err
+	}
+	var str string
+	if err = dec.Decode(&str); err != nil {
+		return err
+	}
+	t, err := FromString(str)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func Join(parts ...string) string {
+	s := strings.Join(parts, string(Separator))
+	if s[0] != Separator {
+		s = string(Separator) + s
+	}
+	return s
+}
+
+func Split(id string) []string {
+	if id[0] != Separator {
+		return nil
+	}
+	return strings.Split(id[1:], string(Separator))
+}
diff --git a/id/object_id_test.go b/id/object_id_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..572f0da96c190a2138875c6b7dcb24c237fbe6fa
--- /dev/null
+++ b/id/object_id_test.go
@@ -0,0 +1,242 @@
+package id
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_ParseID(t *testing.T) {
+	tests := []struct {
+		name      string
+		id        string
+		result    *ObjectId
+		wantError bool
+	}{
+		{
+			name:   "SpaceId",
+			id:     "/spaces/<space_id>",
+			result: MustObjectId("/spaces/<space_id>"),
+		},
+		{
+			name:   "ServiceID",
+			id:     "/services/<service_id>",
+			result: MustObjectId("/services/<service_id>"),
+		},
+		{
+			name:   "UserID",
+			id:     "/users/<user_id>",
+			result: MustObjectId("/users/<user_id>"),
+		},
+		{
+			name:   "OrganizationID",
+			id:     "/orgs/<org_id>",
+			result: MustObjectId("/orgs/<org_id>"),
+		},
+		{
+			name:   "ClientID",
+			id:     "/spaces/<space_id>/clients/<client_id>",
+			result: MustObjectId("/spaces/<space_id>/clients/<client_id>"),
+		},
+		{
+			name:   "RoleID",
+			id:     "/spaces/<space_id>/roles/<role_id>",
+			result: MustObjectId("/spaces/<space_id>/roles/<role_id>"),
+		},
+		{
+			name:   "EnvironmentID",
+			id:     "/spaces/<space_id>/envs/<env_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>"),
+		},
+		{
+			name:   "CollectionId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>"),
+		},
+		{
+			name:   "SchemaID",
+			id:     "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/schema/<collection_id>"),
+		},
+		{
+			name:   "ItemId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>"),
+		},
+		{
+			name:   "RevisionID",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>"),
+		},
+		{
+			name:   "FieldId",
+			id:     "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
+			result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>"),
+		},
+		{
+			name:      "With error #1: no backslash in the beginning of id",
+			id:        "spaces/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #2: backslash in the end of id",
+			id:        "/spaces/<space_id>/",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #3: typo in 'spaces'",
+			id:        "/space/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #4: no space_id in id",
+			id:        "/spaces",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #5: multiple backslashes in the end of id",
+			id:        "/spaces/<space_id>///",
+			result:    nil,
+			wantError: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			id, err := NewObjectId(tt.id)
+			if tt.wantError {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.result, id)
+			require.Equal(t, tt.id, id.String())
+		})
+	}
+}
+
+func Test_Map(t *testing.T) {
+	tests := []struct {
+		name string
+		id   *ObjectId
+	}{
+		{
+			name: "ServiceID",
+			id:   &ObjectId{Descriptor: &ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name: "UserID",
+			id:   &ObjectId{Descriptor: &UserId{UserID: "<user_id>"}},
+		},
+		{
+			name: "OrganizationID",
+			id:   &ObjectId{Descriptor: &OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name: "SpaceId",
+			id:   &ObjectId{Descriptor: &SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name: "ClientID",
+			id: &ObjectId{Descriptor: &ClientId{
+				SpaceId:  SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "RoleID",
+			id: &ObjectId{Descriptor: &RoleId{
+				SpaceId: SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "EnvironmentID",
+			id: &ObjectId{Descriptor: &EnvironmentId{
+				SpaceId:       SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "CollectionId",
+			id: &ObjectId{Descriptor: &CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId:       SpaceId{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "Schema ID",
+			id: &ObjectId{Descriptor: &SchemaId{
+				EnvironmentId: EnvironmentId{
+					SpaceId:       SpaceId{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "ItemId",
+			id: &ObjectId{Descriptor: &ItemId{
+				CollectionId: CollectionId{
+					EnvironmentId: EnvironmentId{
+						SpaceId:       SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "RevisionID",
+			id: &ObjectId{Descriptor: &RevisionId{
+				ItemId: ItemId{
+					CollectionId: CollectionId{
+						EnvironmentId: EnvironmentId{
+							SpaceId:       SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "FieldId",
+			id: &ObjectId{Descriptor: &FieldId{
+				ItemId: ItemId{
+					CollectionId: CollectionId{
+						EnvironmentId: EnvironmentId{
+							SpaceId:       SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field_name>",
+			}},
+		},
+		{
+			name: "SystemID",
+			id:   &ObjectId{Descriptor: &SystemId{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			v, err := FromMap(tt.id.Map())
+			require.NoError(t, err)
+			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
+			assert.Equal(t, v.Map(), tt.id.Map())
+		})
+	}
+}
diff --git a/id/organization.go b/id/organization.go
index fe9d22837977b486e78689ebee5f426bdee80fcf..03fd47fc9f51fbdb67c219c6a636f0f117d032c7 100644
--- a/id/organization.go
+++ b/id/organization.go
@@ -1,49 +1,62 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+)
+
 const (
 	Organization        = "organization"
 	OrganizationsPrefix = "orgs"
 )
 
-type OrganizationID struct {
+var _ Descriptor = &OrganizationId{}
+
+type OrganizationId struct {
 	OrganizationID string `json:"organization_id,omitempty" bson:"organization_id,omitempty"`
 }
 
-func (t *OrganizationID) Type() string { return Organization }
+func (id *OrganizationId) New() Descriptor {
+	return &OrganizationId{}
+}
+
+func (id *OrganizationId) Type() string { return Organization }
 
-func (t *OrganizationID) String() string {
-	return Join(OrganizationsPrefix, t.OrganizationID)
+func (id *OrganizationId) String() string {
+	return Join(OrganizationsPrefix, id.OrganizationID)
 }
 
-func (t *OrganizationID) ToMap() map[string]any {
-	return map[string]any{
-		"organization_id": t.OrganizationID,
-		"type":            Organization,
+func (id *OrganizationId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != OrganizationsPrefix {
+		return ErrInvalidID
 	}
+	id.OrganizationID = parts[1]
+	return nil
 }
 
-func (t *OrganizationID) FromMap(m map[string]any) error {
-	t.OrganizationID = m["organization_id"].(string)
-	return nil
+func (id *OrganizationId) Map() map[string]any {
+	return map[string]any{
+		"organization_id": id.OrganizationID,
+		"type":            id.Type(),
+	}
 }
 
-func (t *OrganizationID) Validate() error {
-	if t.OrganizationID == "" {
-		return ErrInvalidID
+func (id *OrganizationId) FromMap(m map[string]any) error {
+	id.OrganizationID, _ = m["organization_id"].(string)
+	if id.OrganizationID == "" {
+		return fmt.Errorf("%w: OrganizationId required", ErrInvalidID)
 	}
 	return nil
 }
 
-func parseOrganizationID(parts []string) (*OrganizationID, error) {
-	var id OrganizationID
-	if len(parts) != 2 || parts[0] != OrganizationsPrefix {
-		return nil, ErrInvalidID
+func (id *OrganizationId) Validate() error {
+	if id.OrganizationID == "" {
+		return fmt.Errorf("%w: OrganizationId required", ErrInvalidID)
 	}
-
-	id.OrganizationID = parts[1]
-	return &id, nil
+	return nil
 }
 
-func NewOrganizationID(id string) *ID {
-	return &ID{Descriptor: &OrganizationID{OrganizationID: id}}
+func NewOrganizationId(o organizations.Organization) *ObjectId {
+	return &ObjectId{Descriptor: &OrganizationId{OrganizationID: o.ID}}
 }
diff --git a/id/registry.go b/id/registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..89b779c96f98472f391073283274014ff1075756
--- /dev/null
+++ b/id/registry.go
@@ -0,0 +1,118 @@
+package id
+
+import (
+	"fmt"
+	"reflect"
+)
+
+var registry = NewRegistry()
+
+type ObjectHandler func(interface{}) *ObjectId
+
+type Registry struct {
+	handlers    map[reflect.Type]ObjectHandler
+	descriptors map[string]Descriptor
+}
+
+func NewRegistry() *Registry {
+	return &Registry{
+		handlers:    make(map[reflect.Type]ObjectHandler),
+		descriptors: make(map[string]Descriptor),
+	}
+}
+
+// Register method adds a handler based on its type.
+func (r *Registry) RegisterObjectHandler(t reflect.Type, h ObjectHandler) {
+	r.handlers[t] = h
+}
+
+func (r *Registry) RegisterDescriptor(d Descriptor) {
+	r.descriptors[d.Type()] = d
+}
+
+func (r *Registry) FromParts(s string) (*ObjectId, error) {
+	parts := Split(s)
+
+	for _, d := range r.descriptors {
+		id := d.New()
+		if err := id.FromParts(parts); err == nil {
+			return &ObjectId{Descriptor: id}, nil
+		}
+	}
+
+	return nil, fmt.Errorf("%w: %s", ErrInvalidID, s)
+}
+
+func (r *Registry) FromMap(m map[string]interface{}) (*ObjectId, error) {
+	t, ok := m["type"].(string)
+	if !ok {
+		return nil, ErrTypeNotFound
+	}
+
+	if d, ok := r.descriptors[t]; ok {
+		id := d.New()
+		if err := id.FromMap(m); err != nil {
+			return nil, err
+		}
+		return &ObjectId{Descriptor: id}, nil
+	}
+
+	return nil, fmt.Errorf("%s: %s", t, ErrInvalidID)
+}
+
+func (r *Registry) FromObject(v interface{}) (*ObjectId, error) {
+	t := reflect.TypeOf(v)
+	fmt.Println(t.String())
+	fmt.Println(r.handlers)
+	if handler, ok := r.handlers[t]; ok {
+		i := handler(v)
+		if i == nil {
+			panic(fmt.Sprintf("handler for %s returned nil", t))
+		}
+		return i, nil
+	}
+	return nil, fmt.Errorf("%s : %w", t, ErrHandlerNotFound)
+}
+
+func FromMap(m map[string]interface{}) (*ObjectId, error) {
+	return registry.FromMap(m)
+}
+
+func FromString(s string) (*ObjectId, error) {
+	return registry.FromParts(s)
+}
+
+type ObjectIdentifier interface {
+	ObjectId() *ObjectId
+}
+
+func FromObject(v interface{}) (*ObjectId, error) {
+	if id, ok := v.(ObjectIdentifier); ok {
+		return id.ObjectId(), nil
+	}
+	return registry.FromObject(v)
+}
+
+func RegisterSystemIds(r *Registry) {
+	r.RegisterDescriptor(&SpaceId{})
+	r.RegisterDescriptor(&EnvironmentId{})
+	r.RegisterDescriptor(&CollectionId{})
+	r.RegisterDescriptor(&SchemaId{})
+	r.RegisterDescriptor(&ItemId{})
+	r.RegisterDescriptor(&RevisionId{})
+	r.RegisterDescriptor(&FieldId{})
+	r.RegisterDescriptor(&ClientId{})
+	r.RegisterDescriptor(&RoleId{})
+	r.RegisterDescriptor(&UserId{})
+	r.RegisterDescriptor(&SystemId{})
+	r.RegisterDescriptor(&ServiceId{})
+	r.RegisterDescriptor(&OrganizationId{})
+}
+
+func GetRegistry() *Registry {
+	return registry
+}
+
+func init() {
+	RegisterSystemIds(registry)
+}
diff --git a/id/revision.go b/id/revision.go
index 0cb417e132fe1683f1e53a081e7c6244820478df..f8a2ecb4b893d71734e72bd1911a32173a3fd17c 100644
--- a/id/revision.go
+++ b/id/revision.go
@@ -1,61 +1,82 @@
 package id
 
+import "fmt"
+
 const (
 	Revision        = "revision"
 	RevisionsPrefix = "revs"
 )
 
-type RevisionID struct {
-	ItemID
+type RevisionId struct {
+	ItemId
 	RevisionID string `json:"rev_id" bson:"rev_id,omitempty"`
 }
 
-func (t *RevisionID) Type() string { return Revision }
-
-func (t *RevisionID) String() string {
-	return Join(t.ItemID.String(), RevisionsPrefix, t.RevisionID)
+var _ Descriptor = &RevisionId{}
 
+func (id *RevisionId) New() Descriptor {
+	return &RevisionId{}
 }
 
-func (t *RevisionID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
-	m["rev_id"] = t.RevisionID
-	m["type"] = Revision
-	return m
+func (id *RevisionId) Type() string { return Revision }
+
+func (id *RevisionId) String() string {
+	return Join(id.ItemId.String(), RevisionsPrefix, id.RevisionID)
+
 }
 
-func (t *RevisionID) FromMap(m map[string]any) error {
-	if err := t.ItemID.FromMap(m); err != nil {
+func (id *RevisionId) FromParts(parts []string) error {
+	if len(parts) != 10 || parts[8] != RevisionsPrefix {
+		return ErrInvalidID
+	}
+	if err := id.ItemId.FromParts(parts[:8]); err != nil {
 		return err
 	}
-	t.RevisionID = m["rev_id"].(string)
+	id.RevisionID = parts[9]
 	return nil
 }
 
-func (t *RevisionID) Validate() error {
-	if t.RevisionID == "" {
-		return ErrInvalidID
-	}
-
-	return t.ItemID.Validate()
+func (id *RevisionId) Map() map[string]any {
+	m := id.ItemId.Map()
+	m["rev_id"] = id.RevisionID
+	m["type"] = Revision
+	return m
 }
 
-func parseRevisionID(parts []string) (*RevisionID, error) {
-	if len(parts) != 10 || parts[8] != RevisionsPrefix {
-		return nil, ErrInvalidID
+func (id *RevisionId) FromMap(m map[string]any) error {
+	id.RevisionID = m["rev_id"].(string)
+	if id.RevisionID == "" {
+		return fmt.Errorf("%w: RevisionId required", ErrInvalidID)
 	}
 
-	itemID, err := parseItemID(parts[:8])
-	if err != nil {
-		return nil, err
+	if err := id.ItemId.FromMap(m); err != nil {
+		return err
 	}
+	return nil
+}
 
-	var id RevisionID
-	id.ItemID = *itemID
-	id.RevisionID = parts[9]
-	return &id, nil
+func (id *RevisionId) Validate() error {
+	if id.RevisionID == "" {
+		return fmt.Errorf("%w: RevisionId required", ErrInvalidID)
+	}
+
+	return id.ItemId.Validate()
 }
 
-func NewRevisionID(spaceID, envID, collID, itemID, id string) *ID {
-	return &ID{Descriptor: &RevisionID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, RevisionID: id}}
+func NewRevisionID(spaceID, envID, collID, itemID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &RevisionId{
+		ItemId: ItemId{
+			CollectionId: CollectionId{
+				EnvironmentId: EnvironmentId{
+					SpaceId: SpaceId{
+						SpaceID: spaceID,
+					},
+					EnvironmentID: envID,
+				},
+				CollectionID: collID,
+			},
+			ItemID: itemID,
+		},
+		RevisionID: id,
+	}}
 }
diff --git a/id/role.go b/id/role.go
index abb6537fc605d3f01f644a67a724cc81e3a6b54b..4e507d92e6443a7bd890fd5ea0414c1fddebb3c3 100644
--- a/id/role.go
+++ b/id/role.go
@@ -1,61 +1,71 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+)
+
 const (
 	Role        = "role"
 	RolesPrefix = "roles"
 )
 
-type RoleID struct {
-	SpaceID
+var _ Descriptor = &RoleId{}
+
+type RoleId struct {
+	SpaceId
 	RoleID string `json:"role_id,omitempty" bson:"role_id,omitempty"`
 }
 
-func (t *RoleID) Type() string { return Role }
-
-func (t *RoleID) String() string {
-	return Join(t.SpaceID.String(), RolesPrefix, t.RoleID)
-
+func (id *RoleId) New() Descriptor {
+	return &RoleId{}
 }
 
-func (t *RoleID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["role_id"] = t.RoleID
-	m["type"] = Role
-	return m
+func (id *RoleId) Type() string { return Role }
+
+func (id *RoleId) String() string {
+	return Join(id.SpaceId.String(), RolesPrefix, id.RoleID)
 }
 
-func (t *RoleID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *RoleId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != RolesPrefix {
+		return ErrInvalidID
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.RoleID = m["role_id"].(string)
+	id.RoleID = parts[3]
 	return nil
 }
 
-func (t *RoleID) Validate() error {
-	if t.RoleID == "" {
-		return ErrInvalidID
-	}
-
-	return t.SpaceID.Validate()
+func (id *RoleId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["role_id"] = id.RoleID
+	m["type"] = Role
+	return m
 }
 
-func parseRoleID(parts []string) (*RoleID, error) {
-	if len(parts) != 4 || parts[2] != RolesPrefix {
-		return nil, ErrInvalidID
+func (id *RoleId) FromMap(m map[string]any) error {
+	id.RoleID = m["role_id"].(string)
+	if id.RoleID == "" {
+		return fmt.Errorf("%w: RoleID required", ErrInvalidID)
 	}
+	return id.SpaceId.FromMap(m)
+}
 
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *RoleId) Validate() error {
+	if id.RoleID == "" {
+		return fmt.Errorf("%w: RoleID required", ErrInvalidID)
 	}
 
-	var id RoleID
-	id.SpaceID = *spaceID
-	id.RoleID = parts[3]
-	return &id, nil
+	return id.SpaceId.Validate()
 }
-
-func NewRoleID(spaceID, id string) *ID {
-	return &ID{Descriptor: &RoleID{SpaceID: SpaceID{SpaceID: spaceID}, RoleID: id}}
+func NewRoleId(r roles.Role) *ObjectId {
+	return &ObjectId{Descriptor: &RoleId{
+		SpaceId: SpaceId{
+			SpaceID: r.SpaceID,
+		},
+		RoleID: r.ID,
+	}}
 }
diff --git a/id/schema.go b/id/schema.go
index e3afee8c6cf29bca6cae1f1eedd72253c47362a5..b1280f81ca440ae78ce128e471292e26a2db4a97 100644
--- a/id/schema.go
+++ b/id/schema.go
@@ -1,60 +1,70 @@
 package id
 
+import "fmt"
+
 const (
 	Schema       = "schema"
 	SchemaPrefix = "schema"
 )
 
-type SchemaID struct {
-	EnvironmentID
+var _ Descriptor = &SchemaId{}
+
+type SchemaId struct {
+	EnvironmentId
 	CollectionID string `json:"col_id" bson:"col_id,omitempty"`
 }
 
-func (t *SchemaID) Type() string { return Schema }
+func (id *SchemaId) New() Descriptor {
+	return &SchemaId{}
+}
+
+func (id *SchemaId) Type() string { return Schema }
 
-func (t *SchemaID) String() string {
-	return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID)
+func (id *SchemaId) String() string {
+	return Join(id.EnvironmentId.String(), SchemaPrefix, id.CollectionID)
 }
 
-func (t *SchemaID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
-	m["col_id"] = t.CollectionID
+func (id *SchemaId) Map() map[string]any {
+	m := id.EnvironmentId.Map()
+	m["col_id"] = id.CollectionID
 	m["type"] = Schema
 	return m
 }
 
-func (t *SchemaID) FromMap(m map[string]any) error {
-	if err := t.EnvironmentID.FromMap(m); err != nil {
+func (id *SchemaId) FromParts(parts []string) error {
+	if len(parts) != 6 || parts[4] != SchemaPrefix {
+		return ErrInvalidID
+	}
+	if err := id.EnvironmentId.FromParts(parts[:4]); err != nil {
 		return err
 	}
-	t.CollectionID = m["col_id"].(string)
+	id.CollectionID = parts[5]
 	return nil
 }
 
-func (t *SchemaID) Validate() error {
-	if t.CollectionID == "" {
-		return ErrInvalidID
+func (id *SchemaId) FromMap(m map[string]any) error {
+	id.CollectionID, _ = m["col_id"].(string)
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: SchemaID required", ErrInvalidID)
 	}
-
-	return t.EnvironmentID.Validate()
+	return id.EnvironmentId.FromMap(m)
 }
 
-func parseSchemaID(parts []string) (*SchemaID, error) {
-	if len(parts) != 6 || parts[4] != SchemaPrefix {
-		return nil, ErrInvalidID
+func (id *SchemaId) Validate() error {
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: SchemaID required", ErrInvalidID)
 	}
-
-	envID, err := parseEnvironmentID(parts[:4])
-	if err != nil {
-		return nil, err
-	}
-
-	var id SchemaID
-	id.EnvironmentID = *envID
-	id.CollectionID = parts[5]
-	return &id, nil
+	return id.EnvironmentId.Validate()
 }
 
-func NewSchemaID(spaceID, envID, id string) *ID {
-	return &ID{Descriptor: &SchemaID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+func NewSchemaId(spaceID, envID, id string) *ObjectId {
+	return &ObjectId{Descriptor: &SchemaId{
+		EnvironmentId: EnvironmentId{
+			SpaceId: SpaceId{
+				SpaceID: spaceID,
+			},
+			EnvironmentID: envID,
+		},
+		CollectionID: id,
+	}}
 }
diff --git a/id/service.go b/id/service.go
index 23bb23aa500fbba082e1aae578eae6da5624a5b9..29c44ec635f763b9f35223f710f2d530f2dfb159 100644
--- a/id/service.go
+++ b/id/service.go
@@ -1,49 +1,58 @@
 package id
 
+import "fmt"
+
 const (
 	Service        = "service"
 	ServicesPrefix = "services"
 )
 
-type ServiceID struct {
+type ServiceId struct {
 	ServiceID string `json:"service_id,omitempty" bson:"service_id,omitempty"`
 }
 
-func (t *ServiceID) Type() string { return Service }
+var _ Descriptor = &ServiceId{}
 
-func (t *ServiceID) String() string {
-	return Join(ServicesPrefix, t.ServiceID)
+func (id *ServiceId) New() Descriptor {
+	return &ServiceId{}
 }
 
-func (t *ServiceID) ToMap() map[string]any {
-	return map[string]any{
-		"service_id": t.ServiceID,
-		"type":       Service,
-	}
-}
+func (id *ServiceId) Type() string { return Service }
 
-func (t *ServiceID) FromMap(m map[string]any) error {
-	t.ServiceID = m["service_id"].(string)
-	return nil
+func (id *ServiceId) String() string {
+	return Join(ServicesPrefix, id.ServiceID)
 }
 
-func (t *ServiceID) Validate() error {
-	if t.ServiceID == "" {
+func (id *ServiceId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != ServicesPrefix {
 		return ErrInvalidID
 	}
+	id.ServiceID = parts[1]
 	return nil
 }
 
-func parseServiceID(parts []string) (*ServiceID, error) {
-	var id ServiceID
-	if len(parts) != 2 || parts[0] != ServicesPrefix {
-		return nil, ErrInvalidID
+func (id *ServiceId) Map() map[string]any {
+	return map[string]any{
+		"service_id": id.ServiceID,
+		"type":       id.Type(),
 	}
+}
 
-	id.ServiceID = parts[1]
-	return &id, nil
+func (id *ServiceId) FromMap(m map[string]any) error {
+	id.ServiceID, _ = m["service_id"].(string)
+	if id.ServiceID == "" {
+		return fmt.Errorf("%w: ServiceId required", ErrInvalidID)
+	}
+	return nil
+}
+
+func (id *ServiceId) Validate() error {
+	if id.ServiceID == "" {
+		return fmt.Errorf("%w: ServiceId required", ErrInvalidID)
+	}
+	return nil
 }
 
-func NewServiceID(id string) *ID {
-	return &ID{Descriptor: &ServiceID{ServiceID: id}}
+func NewServiceID(id string) *ObjectId {
+	return &ObjectId{Descriptor: &ServiceId{ServiceID: id}}
 }
diff --git a/id/space.go b/id/space.go
index 39096673456d74462ef203b10814396fe48b2bcf..a9d88bc66ba8105f00825674a2015ea16779860f 100644
--- a/id/space.go
+++ b/id/space.go
@@ -1,48 +1,63 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+)
+
 const (
 	Space        = "space"
 	SpacesPrefix = "spaces"
 )
 
-type SpaceID struct {
+var _ Descriptor = &SpaceId{}
+
+type SpaceId struct {
 	SpaceID string `json:"space_id,omitempty" bson:"space_id,omitempty"`
 }
 
-func (t *SpaceID) Type() string { return Space }
+func (id *SpaceId) New() Descriptor {
+	return &SpaceId{}
+}
+
+func (id *SpaceId) Type() string { return Space }
 
-func (t *SpaceID) String() string {
-	return Join(SpacesPrefix, t.SpaceID)
+func (id *SpaceId) String() string {
+	return Join(SpacesPrefix, id.SpaceID)
 }
 
-func (t *SpaceID) ToMap() map[string]any {
-	return map[string]any{
-		"space_id": t.SpaceID,
-		"type":     Space,
+func (id *SpaceId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != SpacesPrefix {
+		return ErrInvalidID
 	}
-}
 
-func (t *SpaceID) FromMap(m map[string]any) error {
-	t.SpaceID = m["space_id"].(string)
+	id.SpaceID = parts[1]
 	return nil
 }
 
-func (t *SpaceID) Validate() error {
-	if t.SpaceID == "" {
-		return ErrInvalidID
+func (id *SpaceId) Map() map[string]any {
+	return map[string]any{
+		"space_id": id.SpaceID,
+		"type":     id.Type(),
 	}
-	return nil
 }
 
-func parseSpaceID(parts []string) (*SpaceID, error) {
-	var id SpaceID
-	if len(parts) != 2 || parts[0] != SpacesPrefix {
-		return nil, ErrInvalidID
+func (id *SpaceId) FromMap(m map[string]any) error {
+	id.SpaceID, _ = m["space_id"].(string)
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalidID)
 	}
+	return nil
+}
 
-	id.SpaceID = parts[1]
-	return &id, nil
+func (id *SpaceId) Validate() error {
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalidID)
+	}
+	return nil
 }
-func NewSpaceID(id string) *ID {
-	return &ID{Descriptor: &SpaceID{SpaceID: id}}
+
+func NewSpaceId(s spaces.Space) *ObjectId {
+	return &ObjectId{Descriptor: &SpaceId{SpaceID: s.ID}}
 }
diff --git a/id/system.go b/id/system.go
index de2f3c2571896e448f8aad17be32df58b73a6ea4..28eb6e04f4e5e7eff20e2b4218c33f8bdcec1fc9 100644
--- a/id/system.go
+++ b/id/system.go
@@ -2,21 +2,23 @@ package id
 
 const System = "system"
 
-type SystemID struct{}
+type SystemId struct{}
 
-func (t *SystemID) Type() string                   { return Space }
-func (t *SystemID) String() string                 { return string(Separator) + System }
-func (t *SystemID) ToMap() map[string]any          { return map[string]any{"type": System} }
-func (t *SystemID) FromMap(m map[string]any) error { return nil }
-func (t *SystemID) Validate() error                { return nil }
+var _ Descriptor = &SystemId{}
 
-func parseSystemID(parts []string) (*SystemID, error) {
-	var id SystemID
+func (id *SystemId) New() Descriptor {
+	return &SystemId{}
+}
+
+func (id *SystemId) Type() string   { return System }
+func (id *SystemId) String() string { return string(Separator) + System }
+func (id *SystemId) FromParts(parts []string) error {
 	if len(parts) != 1 || parts[0] != System {
-		return nil, ErrInvalidID
+		return ErrInvalidID
 	}
-	return &id, nil
-}
-func NewSystemID() *ID {
-	return &ID{Descriptor: &SystemID{}}
+	return nil
 }
+func (id *SystemId) Map() map[string]any            { return map[string]any{"type": System} }
+func (id *SystemId) FromMap(m map[string]any) error { return nil }
+func (id *SystemId) Validate() error                { return nil }
+func (id *SystemId) NewSystemId() *ObjectId         { return &ObjectId{Descriptor: &SystemId{}} }
diff --git a/id/system/system.go b/id/system/system.go
new file mode 100644
index 0000000000000000000000000000000000000000..77084f5dcb8dc702b2b14337e97cdc97fd80930b
--- /dev/null
+++ b/id/system/system.go
@@ -0,0 +1,97 @@
+package system
+
+import (
+	"context"
+	"reflect"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+)
+
+func Handler(obj any) *id.ObjectId {
+	switch val := obj.(type) {
+	case *organizations.Organization:
+		var i id.OrganizationId
+		i.OrganizationID = val.ID
+		return id.MustObjectId(&i)
+	case *spaces.Space:
+		var i id.SpaceId
+		i.SpaceID = val.ID
+		return id.MustObjectId(&i)
+	case *environments.Environment:
+		var i id.EnvironmentId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.ID
+		return id.MustObjectId(&i)
+	case *collections.Collection:
+		var i id.CollectionId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.ID
+		return id.MustObjectId(&i)
+	case *items.Item:
+		var i id.ItemId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.CollectionID
+		i.ItemID = val.ID
+		return id.MustObjectId(&i)
+	case *users.User:
+		var i id.UserId
+		i.UserID = val.ID
+		return id.MustObjectId(&i)
+	case *clients.Client:
+		var i id.ClientId
+		i.SpaceID = val.SpaceID
+		i.ClientID = val.ID
+		return id.MustObjectId(&i)
+	case *roles.Role:
+		var i id.RoleId
+		i.SpaceID = val.SpaceID
+		i.RoleID = val.ID
+		return id.MustObjectId(&i)
+	case *auth.UserPrincipal:
+		var i id.UserId
+		i.UserID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	case *auth.ClientPrincipal:
+		var i id.ClientId
+		i.ClientID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	case *auth.SystemPrincipal:
+		return id.MustObjectId(&id.SystemId{})
+	case *auth.Anonymous:
+		var i id.UserId
+		i.UserID = val.GetID(context.TODO())
+		return id.MustObjectId(&i)
+	}
+	return nil
+}
+
+// Register registers object handler for system types into the provided Registry.
+func Register(r *id.Registry) {
+	r.RegisterObjectHandler(reflect.TypeOf(&organizations.Organization{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&spaces.Space{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&environments.Environment{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&collections.Collection{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&items.Item{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&clients.Client{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&roles.Role{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&users.User{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.UserPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.ClientPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.SystemPrincipal{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&auth.Anonymous{}), Handler)
+}
+
+func init() {
+	Register(id.GetRegistry())
+}
diff --git a/id/test/object_id_test.go b/id/test/object_id_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f97e50e49068173b5aa1968de33abc4d8a44817b
--- /dev/null
+++ b/id/test/object_id_test.go
@@ -0,0 +1,801 @@
+package test
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/id"
+	_ "git.perx.ru/perxis/perxis-go/id/system"
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/organizations"
+	"git.perx.ru/perxis/perxis-go/pkg/roles"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_OrganizationId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &organizations.Organization{ID: "<org_id>"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "organization", "organization_id": "<org_id>"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "organization"},
+			out:    "/orgs/<org_id>",
+			result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ServiceId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "service", "service_id": "<service_id>"},
+			out:    "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "service"},
+			out:    "/services/<service_id>",
+			result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_UserId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &users.User{ID: "<user_id>"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "user", "user_id": "<user_id>"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "user"},
+			out:    "/users/<user_id>",
+			result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_SpaceId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &spaces.Space{ID: "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "space", "space_id": "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "space"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+			err:    id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_EnvironmentId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &environments.Environment{SpaceID: "<space_id>", ID: "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "environment", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ClientId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &clients.Client{SpaceID: "<space_id>", ID: "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>", "client_id": "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "client", "client_id": "<client_id>"},
+			out:  "/spaces/<space_id>/clients/<client_id>",
+			result: &id.ObjectId{Descriptor: &id.ClientId{
+				SpaceId:  id.SpaceId{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_RoleId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "valid object",
+			in:   &roles.Role{SpaceID: "<space_id>", ID: "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "role", "space_id": "<space_id>", "role_id": "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "client", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "role", "role_id": "<role_id>"},
+			out:  "/spaces/<space_id>/roles/<role_id>",
+			result: &id.ObjectId{Descriptor: &id.RoleId{
+				SpaceId: id.SpaceId{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+			err: id.ErrInvalidID,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_CollectionId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &collections.Collection{SpaceID: "<space_id>", EnvID: "<env_id>", ID: "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "collection", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_SchemaId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.SchemaId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "schema", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.SchemaId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ItemId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &items.Item{SpaceID: "<space_id>", EnvID: "<env_id>", CollectionID: "<collection_id>", ID: "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "item", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_FieldId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "field", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "field": "<field>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_RevisionId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: &id.ObjectId{Descriptor: &id.RevisionId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "revision", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "rev_id": "<rev_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: &id.ObjectId{Descriptor: &id.RevisionId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
diff --git a/id/user.go b/id/user.go
index c76f6c9fa0e8ce440a1ef15e36bbb82a73301be5..8b4100b01039f5ebd65722e2521645304df31b04 100644
--- a/id/user.go
+++ b/id/user.go
@@ -1,49 +1,62 @@
 package id
 
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+)
+
 const (
 	User        = "user"
 	UsersPrefix = "users"
 )
 
-type UserID struct {
+type UserId struct {
 	UserID string `json:"user_id,omitempty" bson:"user_id,omitempty"`
 }
 
-func (t *UserID) Type() string { return User }
+var _ Descriptor = &UserId{}
 
-func (t *UserID) String() string {
-	return Join(UsersPrefix, t.UserID)
+func (id *UserId) New() Descriptor {
+	return &UserId{}
 }
 
-func (t *UserID) ToMap() map[string]any {
-	return map[string]any{
-		"user_id": t.UserID,
-		"type":    User,
-	}
-}
+func (id *UserId) Type() string { return User }
 
-func (t *UserID) FromMap(m map[string]any) error {
-	t.UserID = m["user_id"].(string)
-	return nil
+func (id *UserId) String() string {
+	return Join(UsersPrefix, id.UserID)
 }
 
-func (t *UserID) Validate() error {
-	if t.UserID == "" {
+func (id *UserId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != UsersPrefix {
 		return ErrInvalidID
 	}
+	id.UserID = parts[1]
 	return nil
 }
 
-func parseUserID(parts []string) (*UserID, error) {
-	var id UserID
-	if len(parts) != 2 || parts[0] != UsersPrefix {
-		return nil, ErrInvalidID
+func (id *UserId) Map() map[string]any {
+	return map[string]any{
+		"user_id": id.UserID,
+		"type":    id.Type(),
 	}
+}
 
-	id.UserID = parts[1]
-	return &id, nil
+func (id *UserId) FromMap(m map[string]any) error {
+	id.UserID, _ = m["user_id"].(string)
+	if id.UserID == "" {
+		return fmt.Errorf("%w: UserId required", ErrInvalidID)
+	}
+	return nil
+}
+
+func (id *UserId) Validate() error {
+	if id.UserID == "" {
+		return fmt.Errorf("%w: UserId required", ErrInvalidID)
+	}
+	return nil
 }
 
-func NewUserID(id string) *ID {
-	return &ID{Descriptor: &UserID{UserID: id}}
+func NewUserId(u users.User) *ObjectId {
+	return &ObjectId{Descriptor: &UserId{UserID: u.ID}}
 }
diff --git a/log/log.go b/log/log.go
index d21ddbcaaa262c4c16e95a35cd098f77ea4a53cb..05aff704243a60e52c70a7ee57cdb50f9c5884e7 100644
--- a/log/log.go
+++ b/log/log.go
@@ -27,17 +27,17 @@ func (l Level) String() string {
 }
 
 type Entry struct {
-	ID        string      `json:"id" bson:"id" mapstructure:"id"`
-	Timestamp time.Time   `json:"timestamp,omitempty" bson:"timestamp,omitempty" mapstructure:"timestamp,omitempty"`
-	LogLevel  Level       `json:"log_level,omitempty" bson:"log_level,omitempty" mapstructure:"log_level,omitempty"`
-	Message   string      `json:"message,omitempty" bson:"message,omitempty" mapstructure:"message,omitempty"`
-	Category  string      `json:"category,omitempty" bson:"category,omitempty" mapstructure:"category,omitempty"`
-	Component string      `json:"component,omitempty" bson:"component,omitempty" mapstructure:"component,omitempty"`
-	Event     string      `json:"event,omitempty" bson:"event,omitempty" mapstructure:"event,omitempty"`
-	ObjectID  *id.ID      `json:"object_id,omitempty" bson:"object_id,omitempty" mapstructure:"object_id,omitempty"`
-	CallerID  *id.ID      `json:"caller_id,omitempty" bson:"caller_id,omitempty" mapstructure:"caller_id,omitempty"`
-	Attr      interface{} `json:"attr,omitempty" bson:"attr,omitempty" mapstructure:"attr,omitempty"`
-	Tags      []string    `json:"tags,omitempty" bson:"tags,omitempty" mapstructure:"tags,omitempty"`
+	ID        string       `json:"id" bson:"id" mapstructure:"id"`
+	Timestamp time.Time    `json:"timestamp,omitempty" bson:"timestamp,omitempty" mapstructure:"timestamp,omitempty"`
+	LogLevel  Level        `json:"log_level,omitempty" bson:"log_level,omitempty" mapstructure:"log_level,omitempty"`
+	Message   string       `json:"message,omitempty" bson:"message,omitempty" mapstructure:"message,omitempty"`
+	Category  string       `json:"category,omitempty" bson:"category,omitempty" mapstructure:"category,omitempty"`
+	Component string       `json:"component,omitempty" bson:"component,omitempty" mapstructure:"component,omitempty"`
+	Event     string       `json:"event,omitempty" bson:"event,omitempty" mapstructure:"event,omitempty"`
+	ObjectID  *id.ObjectId `json:"object_id,omitempty" bson:"object_id,omitempty" mapstructure:"object_id,omitempty"`
+	CallerID  *id.ObjectId `json:"caller_id,omitempty" bson:"caller_id,omitempty" mapstructure:"caller_id,omitempty"`
+	Attr      interface{}  `json:"attr,omitempty" bson:"attr,omitempty" mapstructure:"attr,omitempty"`
+	Tags      []string     `json:"tags,omitempty" bson:"tags,omitempty" mapstructure:"tags,omitempty"`
 }
 
 //func convertInterfaceToAny(v interface{}) (*any.Any, error) {
@@ -84,10 +84,10 @@ func EntryFromPB(request *pb.LogEntry) *Entry {
 	}
 
 	if request.ObjectId != "" {
-		logEntry.ObjectID, _ = id.Parse(request.ObjectId)
+		logEntry.ObjectID, _ = id.NewObjectId(request.ObjectId)
 	}
 	if request.CallerId != "" {
-		logEntry.CallerID, _ = id.Parse(request.CallerId)
+		logEntry.CallerID, _ = id.NewObjectId(request.CallerId)
 	}
 	if request.Attr != nil {
 		logEntry.Attr = request.Attr // todo: как с этим работать?
@@ -111,11 +111,11 @@ func (e *Entry) ToMap() map[string]any {
 	}
 	if e.ObjectID != nil {
 		res["object_id"] = e.ObjectID.String()
-		res["object"] = e.ObjectID.ToMap()
+		res["object"] = e.ObjectID.Map()
 	}
 	if e.CallerID != nil {
 		res["caller_id"] = e.CallerID.String()
-		res["caller"] = e.CallerID.ToMap()
+		res["caller"] = e.CallerID.Map()
 	}
 	if e.Attr != nil {
 		res["attr"] = e.Attr
diff --git a/log/log_test.go b/log/log_test.go
index 08d78f594dcaeae0c72557401f70c4d026e918b2..72ab7d3c10459a8be5464f9bd51546f33df3e5f7 100644
--- a/log/log_test.go
+++ b/log/log_test.go
@@ -17,8 +17,8 @@ func TestEntry_ToMap(t *testing.T) {
 		Category  string
 		Component string
 		Event     string
-		ObjectId  *id.ID
-		CallerId  *id.ID
+		ObjectId  *id.ObjectId
+		CallerId  *id.ObjectId
 		Attr      interface{}
 		Tags      []string
 	}
@@ -37,8 +37,8 @@ func TestEntry_ToMap(t *testing.T) {
 				"",
 				"",
 				"",
-				id.NewEnvironmentID("<space_id>", "<env_id>"),
-				id.NewUserID("<user_id>"),
+				id.MustObjectId("/spaces/<space_id>/envs/<env_id>"),
+				id.MustObjectId("/users/<user_id>"),
 				nil,
 				nil,
 			},