Repository: yorkie-team/yorkie
Pull request #349 (2022-06-28 10:25:48)

Add history command to CLI
<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

Add history command to CLI

```
$ yorkie history --help
Show the history of a document

Usage:
  yorkie history [project name] [document key] [flags]

Flags:
  -h, --help   help for history

$ yorkie history default codemirror
 SEQ  MESSAGE                                       SNAPSHOT
   1  create content if not exists                  {"content":""}
   2  update selection by 62babe7f977bc0f1cb53f7c3  {"content":""}
   3  update content by 62babe7f977bc0f1cb53f7c3    {"content":"a"}
   4  update content by 62babe7f977bc0f1cb53f7c3    {"content":"ab"}
   5  update content by 62babe7f977bc0f1cb53f7c3    {"content":"abc"}
   6  update content by 62babe7f977bc0f1cb53f7c3    {"content":"abcd"}
   7  update content by 62babe7f977bc0f1cb53f7c3    {"content":"abcde"}
   8  update content by 62babe7f977bc0f1cb53f7c3    {"content":"abcdef"}
```

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

**Additional documentation**:

<!--
This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation:
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

**Checklist**:
- [x] Added relevant tests or not required
- [x] Didn't break anything


codecov[bot]: # [Codecov](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#349](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (47eb9de) into [main](https://codecov.io/gh/yorkie-team/yorkie/commit/f6a4aa15ea4cf3a13ae1288e1f9b191ac536aee0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (f6a4aa1) will **increase** coverage by `0.10%`.
> The diff coverage is `10.34%`.

```diff
@@            Coverage Diff             @@
##             main     #349      +/-   ##
==========================================
+ Coverage   48.36%   48.47%   +0.10%     
==========================================
  Files          66       66              
  Lines        5485     5446      -39     
==========================================
- Hits         2653     2640      -13     
+ Misses       2553     2523      -30     
- Partials      279      283       +4     
```


| [Impacted Files](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [api/types/auth\_webhook.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL3R5cGVzL2F1dGhfd2ViaG9vay5nbw==) | `45.45% <ø> (-2.38%)` | :arrow_down: |
| [client/client.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-Y2xpZW50L2NsaWVudC5nbw==) | `12.08% <ø> (+0.88%)` | :arrow_up: |
| [server/backend/database/memory/database.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbWVtb3J5L2RhdGFiYXNlLmdv) | `47.12% <0.00%> (-0.93%)` | :arrow_down: |
| [server/backend/database/mongo/client.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbW9uZ28vY2xpZW50Lmdv) | `9.13% <0.00%> (-0.29%)` | :arrow_down: |
| [server/rpc/yorkie\_server.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL3JwYy95b3JraWVfc2VydmVyLmdv) | `46.42% <100.00%> (+5.80%)` | :arrow_up: |
| [pkg/splay/splay.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL3NwbGF5L3NwbGF5Lmdv) | `83.33% <0.00%> (-4.63%)` | :arrow_down: |
| [pkg/document/json/rga\_tree\_split.go](https://codecov.io/gh/yorkie-team/yorkie/pull/349/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2pzb24vcmdhX3RyZWVfc3BsaXQuZ28=) | `73.93% <0.00%> (-1.07%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team). Last update [f6a4aa1...47eb9de](https://codecov.io/gh/yorkie-team/yorkie/pull/349?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team).


Patch:
diff --git a/admin/client.go b/admin/client.go
index 04c78372..8a73be7b 100644
--- a/admin/client.go
+++ b/admin/client.go
@@ -26,6 +26,8 @@ import (
 	"github.com/yorkie-team/yorkie/api"
 	"github.com/yorkie-team/yorkie/api/converter"
 	"github.com/yorkie-team/yorkie/api/types"
+	"github.com/yorkie-team/yorkie/pkg/document"
+	"github.com/yorkie-team/yorkie/pkg/document/key"
 )
 
 // Option configures Options.
@@ -157,3 +159,41 @@ func (c *Client) UpdateProject(
 
 	return converter.FromProject(response.Project)
 }
+
+// ListChangeSummaries returns the change summaries of the given document.
+func (c *Client) ListChangeSummaries(
+	ctx context.Context,
+	projectName string,
+	key key.Key,
+) ([]*types.ChangeSummary, error) {
+	resp, err := c.client.ListChanges(ctx, &api.ListChangesRequest{
+		ProjectName: projectName,
+		DocumentKey: key.String(),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	changes, err := converter.FromChanges(resp.Changes)
+	if err != nil {
+		return nil, err
+	}
+
+	doc := document.NewInternalDocument(key)
+
+	var summaries []*types.ChangeSummary
+	for _, c := range changes {
+		if err := doc.ApplyChanges(c); err != nil {
+			return nil, err
+		}
+
+		// TODO(hackerwins): doc.Marshal is expensive function. We need to optimize it.
+		summaries = append(summaries, &types.ChangeSummary{
+			ID:       c.ID(),
+			Message:  c.Message(),
+			Snapshot: doc.Marshal(),
+		})
+	}
+
+	return summaries, nil
+}
diff --git a/api/admin.pb.go b/api/admin.pb.go
index d490c5e5..def4f8c0 100644
--- a/api/admin.pb.go
+++ b/api/admin.pb.go
@@ -622,6 +622,108 @@ func (m *GetDocumentResponse) GetDocument() *DocumentSummary {
 	return nil
 }
 
+type ListChangesRequest struct {
+	ProjectName          string   `protobuf:"bytes,1,opt,name=project_name,json=projectName,proto3" json:"project_name,omitempty"`
+	DocumentKey          string   `protobuf:"bytes,2,opt,name=document_key,json=documentKey,proto3" json:"document_key,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ListChangesRequest) Reset()         { *m = ListChangesRequest{} }
+func (m *ListChangesRequest) String() string { return proto.CompactTextString(m) }
+func (*ListChangesRequest) ProtoMessage()    {}
+func (*ListChangesRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_73a7fc70dcc2027c, []int{12}
+}
+func (m *ListChangesRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ListChangesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ListChangesRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ListChangesRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListChangesRequest.Merge(m, src)
+}
+func (m *ListChangesRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *ListChangesRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListChangesRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListChangesRequest proto.InternalMessageInfo
+
+func (m *ListChangesRequest) GetProjectName() string {
+	if m != nil {
+		return m.ProjectName
+	}
+	return ""
+}
+
+func (m *ListChangesRequest) GetDocumentKey() string {
+	if m != nil {
+		return m.DocumentKey
+	}
+	return ""
+}
+
+type ListChangesResponse struct {
+	Changes              []*Change `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
+	XXX_unrecognized     []byte    `json:"-"`
+	XXX_sizecache        int32     `json:"-"`
+}
+
+func (m *ListChangesResponse) Reset()         { *m = ListChangesResponse{} }
+func (m *ListChangesResponse) String() string { return proto.CompactTextString(m) }
+func (*ListChangesResponse) ProtoMessage()    {}
+func (*ListChangesResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_73a7fc70dcc2027c, []int{13}
+}
+func (m *ListChangesResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ListChangesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ListChangesResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ListChangesResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListChangesResponse.Merge(m, src)
+}
+func (m *ListChangesResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *ListChangesResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListChangesResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListChangesResponse proto.InternalMessageInfo
+
+func (m *ListChangesResponse) GetChanges() []*Change {
+	if m != nil {
+		return m.Changes
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterType((*CreateProjectRequest)(nil), "api.CreateProjectRequest")
 	proto.RegisterType((*CreateProjectResponse)(nil), "api.CreateProjectResponse")
@@ -635,46 +737,51 @@ func init() {
 	proto.RegisterType((*ListDocumentsResponse)(nil), "api.ListDocumentsResponse")
 	proto.RegisterType((*GetDocumentRequest)(nil), "api.GetDocumentRequest")
 	proto.RegisterType((*GetDocumentResponse)(nil), "api.GetDocumentResponse")
+	proto.RegisterType((*ListChangesRequest)(nil), "api.ListChangesRequest")
+	proto.RegisterType((*ListChangesResponse)(nil), "api.ListChangesResponse")
 }
 
 func init() { proto.RegisterFile("admin.proto", fileDescriptor_73a7fc70dcc2027c) }
 
 var fileDescriptor_73a7fc70dcc2027c = []byte{
-	// 537 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x4c,
-	0x14, 0x8d, 0x93, 0xb6, 0x5f, 0x7c, 0x9d, 0x7e, 0xc0, 0x34, 0x01, 0xd7, 0x15, 0x21, 0x9d, 0x05,
-	0x44, 0x2c, 0x22, 0x94, 0x6e, 0x91, 0x0a, 0x05, 0xda, 0xa2, 0x22, 0x84, 0x5c, 0xb1, 0x81, 0x85,
-	0x35, 0x8d, 0x6f, 0xd1, 0x40, 0x1d, 0x1b, 0x8f, 0x0d, 0x4a, 0x1f, 0x04, 0xf1, 0x1a, 0xbc, 0x05,
-	0x4b, 0x1e, 0x01, 0x85, 0x17, 0x41, 0x19, 0xcf, 0x38, 0xf1, 0xcf, 0x82, 0xec, 0xa2, 0x73, 0xcf,
-	0x3d, 0x73, 0xcf, 0xf5, 0xb9, 0x01, 0x8b, 0xf9, 0x01, 0x9f, 0x8e, 0xa2, 0x38, 0x4c, 0x42, 0xd2,
-	0x62, 0x11, 0x77, 0x6e, 0xc4, 0x28, 0xc2, 0x34, 0x9e, 0xa0, 0xc8, 0x50, 0xfa, 0x10, 0xba, 0xcf,
-	0x62, 0x64, 0x09, 0xbe, 0x89, 0xc3, 0x8f, 0x38, 0x49, 0x5c, 0xfc, 0x9c, 0xa2, 0x48, 0x08, 0x81,
-	0x8d, 0x29, 0x0b, 0xd0, 0x36, 0x06, 0xc6, 0xd0, 0x74, 0xe5, 0x6f, 0x7a, 0x08, 0xbd, 0x12, 0x57,
-	0x44, 0xe1, 0x54, 0x20, 0xb9, 0x0f, 0xff, 0x45, 0x19, 0x24, 0xf9, 0xd6, 0xb8, 0x33, 0x62, 0x11,
-	0x1f, 0x69, 0x9a, 0x2e, 0xd2, 0x07, 0x70, 0xeb, 0x04, 0x93, 0x7f, 0x78, 0xe9, 0x31, 0x90, 0x55,
-	0xe2, 0x9a, 0xcf, 0xf4, 0x60, 0xe7, 0x15, 0x17, 0xba, 0x5d, 0xa8, 0x87, 0xe8, 0x13, 0xe8, 0x16,
-	0x61, 0x25, 0x3b, 0x84, 0xb6, 0xea, 0x14, 0xb6, 0x31, 0x68, 0x55, 0x74, 0xf3, 0x2a, 0x7d, 0x0f,
-	0xdd, 0xb7, 0x91, 0x5f, 0x5d, 0xd6, 0xff, 0xd0, 0xe4, 0xbe, 0x32, 0xd0, 0xe4, 0x3e, 0x39, 0x80,
-	0xad, 0x4b, 0x8e, 0x57, 0xbe, 0xb0, 0x9b, 0x72, 0xce, 0x3d, 0xa9, 0x27, 0x5b, 0xd9, 0xc5, 0x95,
-	0xee, 0x3e, 0x96, 0x14, 0x57, 0x51, 0x17, 0xdb, 0x2d, 0x89, 0xaf, 0x69, 0xfb, 0x9b, 0x91, 0x19,
-	0x7c, 0x1e, 0x4e, 0xd2, 0x00, 0xa7, 0xb9, 0x71, 0xb2, 0x0f, 0x1d, 0xc5, 0xf1, 0x56, 0x36, 0x6d,
-	0x29, 0xec, 0x35, 0x0b, 0x90, 0xdc, 0x03, 0x2b, 0x8a, 0xf1, 0x0b, 0x0f, 0x53, 0xe1, 0x71, 0x5f,
-	0x8e, 0x6d, 0xba, 0xa0, 0xa1, 0x97, 0x3e, 0xd9, 0x03, 0x33, 0x62, 0x1f, 0xd0, 0x13, 0xfc, 0x1a,
-	0xed, 0xd6, 0xc0, 0x18, 0x6e, 0xba, 0xed, 0x05, 0x70, 0xce, 0xaf, 0x91, 0xdc, 0x05, 0xe0, 0xc2,
-	0xbb, 0x0c, 0xe3, 0xaf, 0x2c, 0xf6, 0xed, 0x8d, 0x81, 0x31, 0x6c, 0xbb, 0x26, 0x17, 0xc7, 0x19,
-	0x40, 0xcf, 0xa0, 0x57, 0x9a, 0x4b, 0x39, 0x1b, 0x83, 0xe9, 0x6b, 0x50, 0xad, 0xbe, 0x2b, 0xbd,
-	0x69, 0xea, 0x79, 0x1a, 0x04, 0x2c, 0x9e, 0xb9, 0x4b, 0x1a, 0x7d, 0x27, 0xa3, 0xa1, 0x09, 0x6b,
-	0x58, 0xdc, 0x87, 0x8e, 0x56, 0xf1, 0x3e, 0xe1, 0x4c, 0x79, 0xb4, 0x34, 0x76, 0x86, 0x33, 0x7a,
-	0x02, 0x3b, 0x05, 0x6d, 0x35, 0xe6, 0x23, 0x68, 0x6b, 0x96, 0xfa, 0x02, 0xf5, 0x53, 0xe6, 0xac,
-	0xf1, 0x8f, 0x16, 0x6c, 0x3e, 0x5d, 0xdc, 0x1e, 0x39, 0x85, 0xed, 0xc2, 0xcd, 0x90, 0x5d, 0xd9,
-	0x5a, 0x77, 0x73, 0x8e, 0x53, 0x57, 0xca, 0x66, 0xa0, 0x0d, 0xf2, 0x02, 0x3a, 0xab, 0xf1, 0x25,
-	0xb6, 0x64, 0xd7, 0x04, 0xdd, 0xd9, 0xad, 0xa9, 0xe4, 0x32, 0x87, 0x00, 0xcb, 0xd3, 0x22, 0xb7,
-	0x25, 0xb5, 0x72, 0x94, 0xce, 0x9d, 0x0a, 0x9e, 0x0b, 0x9c, 0xc2, 0x76, 0x21, 0xa7, 0xca, 0x51,
-	0xdd, 0x61, 0x28, 0x47, 0xb5, 0xb1, 0xce, 0x94, 0x0a, 0xb9, 0x20, 0xcb, 0xc1, 0xcb, 0x19, 0x56,
-	0x4a, 0xb5, 0x31, 0xa2, 0x0d, 0x72, 0x04, 0xd6, 0xca, 0x87, 0x23, 0xf9, 0xf4, 0xa5, 0x98, 0x38,
-	0x76, 0xb5, 0xa0, 0x35, 0x8e, 0x6e, 0xfe, 0x9c, 0xf7, 0x8d, 0x5f, 0xf3, 0xbe, 0xf1, 0x7b, 0xde,
-	0x37, 0xbe, 0xff, 0xe9, 0x37, 0x2e, 0xb6, 0xe4, 0x5f, 0xe4, 0xc1, 0xdf, 0x00, 0x00, 0x00, 0xff,
-	0xff, 0xc0, 0x3e, 0x98, 0x97, 0x47, 0x05, 0x00, 0x00,
+	// 583 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcb, 0x6e, 0xd3, 0x40,
+	0x14, 0x8d, 0x93, 0x3e, 0x92, 0xeb, 0x94, 0xc7, 0x34, 0x01, 0xd7, 0x15, 0x21, 0x1d, 0x09, 0x88,
+	0x58, 0x44, 0x28, 0xdd, 0x56, 0x2a, 0xb4, 0xd0, 0x16, 0x15, 0x21, 0xe4, 0x8a, 0x0d, 0x2c, 0xa2,
+	0x69, 0x7c, 0x5b, 0x06, 0xea, 0xd8, 0x78, 0x12, 0x50, 0xfa, 0x21, 0x88, 0x4f, 0x62, 0xc9, 0x27,
+	0xa0, 0xf0, 0x0d, 0xec, 0x51, 0xe6, 0xe1, 0x38, 0xb6, 0x17, 0x44, 0x62, 0x17, 0x9d, 0x7b, 0xe6,
+	0xcc, 0x39, 0xd7, 0xf7, 0x4e, 0xc0, 0x66, 0x7e, 0xc0, 0x87, 0xdd, 0x28, 0x0e, 0x47, 0x21, 0xa9,
+	0xb0, 0x88, 0xbb, 0x37, 0x63, 0x14, 0xe1, 0x38, 0x1e, 0xa0, 0x50, 0x28, 0x7d, 0x0c, 0x8d, 0xc3,
+	0x18, 0xd9, 0x08, 0xdf, 0xc4, 0xe1, 0x47, 0x1c, 0x8c, 0x3c, 0xfc, 0x3c, 0x46, 0x31, 0x22, 0x04,
+	0x56, 0x86, 0x2c, 0x40, 0xc7, 0x6a, 0x5b, 0x9d, 0x9a, 0x27, 0x7f, 0xd3, 0x7d, 0x68, 0x66, 0xb8,
+	0x22, 0x0a, 0x87, 0x02, 0xc9, 0x43, 0x58, 0x8f, 0x14, 0x24, 0xf9, 0x76, 0xaf, 0xde, 0x65, 0x11,
+	0xef, 0x1a, 0x9a, 0x29, 0xd2, 0x47, 0x70, 0xfb, 0x18, 0x47, 0xff, 0x70, 0xd3, 0x1e, 0x90, 0x34,
+	0x71, 0xc9, 0x6b, 0x9a, 0xb0, 0xf9, 0x8a, 0x0b, 0x73, 0x5c, 0xe8, 0x8b, 0xe8, 0x53, 0x68, 0x2c,
+	0xc2, 0x5a, 0xb6, 0x03, 0x55, 0x7d, 0x52, 0x38, 0x56, 0xbb, 0x92, 0xd3, 0x4d, 0xaa, 0xf4, 0x3d,
+	0x34, 0xde, 0x46, 0x7e, 0xbe, 0x59, 0x37, 0xa0, 0xcc, 0x7d, 0x1d, 0xa0, 0xcc, 0x7d, 0xb2, 0x0b,
+	0x6b, 0x17, 0x1c, 0xaf, 0x7c, 0xe1, 0x94, 0xa5, 0xcf, 0x6d, 0xa9, 0x27, 0x8f, 0xb2, 0xf3, 0x2b,
+	0x73, 0xfa, 0x48, 0x52, 0x3c, 0x4d, 0x9d, 0x75, 0x37, 0x23, 0xbe, 0x64, 0xec, 0x6f, 0x96, 0x0a,
+	0xf8, 0x3c, 0x1c, 0x8c, 0x03, 0x1c, 0x26, 0xc1, 0xc9, 0x0e, 0xd4, 0x35, 0xa7, 0x9f, 0xea, 0xb4,
+	0xad, 0xb1, 0xd7, 0x2c, 0x40, 0x72, 0x1f, 0xec, 0x28, 0xc6, 0x2f, 0x3c, 0x1c, 0x8b, 0x3e, 0xf7,
+	0xa5, 0xed, 0x9a, 0x07, 0x06, 0x7a, 0xe9, 0x93, 0x6d, 0xa8, 0x45, 0xec, 0x12, 0xfb, 0x82, 0x5f,
+	0xa3, 0x53, 0x69, 0x5b, 0x9d, 0x55, 0xaf, 0x3a, 0x03, 0xce, 0xf8, 0x35, 0x92, 0x7b, 0x00, 0x5c,
+	0xf4, 0x2f, 0xc2, 0xf8, 0x2b, 0x8b, 0x7d, 0x67, 0xa5, 0x6d, 0x75, 0xaa, 0x5e, 0x8d, 0x8b, 0x23,
+	0x05, 0xd0, 0x53, 0x68, 0x66, 0x7c, 0xe9, 0x64, 0x3d, 0xa8, 0xf9, 0x06, 0xd4, 0xad, 0x6f, 0xc8,
+	0x6c, 0x86, 0x7a, 0x36, 0x0e, 0x02, 0x16, 0x4f, 0xbc, 0x39, 0x8d, 0xbe, 0x93, 0xa3, 0x61, 0x08,
+	0x4b, 0x44, 0xdc, 0x81, 0xba, 0x51, 0xe9, 0x7f, 0xc2, 0x89, 0xce, 0x68, 0x1b, 0xec, 0x14, 0x27,
+	0xf4, 0x18, 0x36, 0x17, 0xb4, 0xb5, 0xcd, 0x27, 0x50, 0x35, 0x2c, 0xfd, 0x05, 0x8a, 0x5d, 0x26,
+	0xac, 0x99, 0xc9, 0x59, 0xe2, 0xc3, 0x0f, 0x6c, 0x78, 0x89, 0xe2, 0xff, 0x9a, 0xdc, 0x53, 0xd3,
+	0x9d, 0x68, 0x6b, 0x93, 0x0f, 0x60, 0x7d, 0xa0, 0x20, 0xdd, 0x49, 0x5b, 0x7a, 0x54, 0x34, 0xcf,
+	0xd4, 0x7a, 0x7f, 0x2a, 0xb0, 0xfa, 0x6c, 0xf6, 0x2a, 0x90, 0x13, 0xd8, 0x58, 0xd8, 0x66, 0xb2,
+	0xa5, 0x0e, 0x14, 0xbc, 0x06, 0xae, 0x5b, 0x54, 0x52, 0x17, 0xd3, 0x12, 0x79, 0x01, 0xf5, 0xf4,
+	0x62, 0x11, 0x47, 0xb2, 0x0b, 0x56, 0xd0, 0xdd, 0x2a, 0xa8, 0x24, 0x32, 0xfb, 0x00, 0xf3, 0xa5,
+	0x27, 0x77, 0x24, 0x35, 0xf7, 0x5c, 0xb8, 0x77, 0x73, 0x78, 0x22, 0x70, 0x02, 0x1b, 0x0b, 0x1b,
+	0xa4, 0x13, 0x15, 0xad, 0xac, 0x4e, 0x54, 0xb8, 0x70, 0x4a, 0x69, 0x61, 0x62, 0xc9, 0xdc, 0x78,
+	0x76, 0xbb, 0xb4, 0x52, 0xe1, 0x80, 0xd3, 0x12, 0x39, 0x00, 0x3b, 0x35, 0x52, 0x24, 0x71, 0x9f,
+	0x19, 0x60, 0xd7, 0xc9, 0x17, 0xd2, 0x1a, 0xa9, 0x2f, 0xae, 0x35, 0xf2, 0xf3, 0xe5, 0x3a, 0xf9,
+	0x82, 0xd1, 0x38, 0xb8, 0xf5, 0x63, 0xda, 0xb2, 0x7e, 0x4e, 0x5b, 0xd6, 0xaf, 0x69, 0xcb, 0xfa,
+	0xfe, 0xbb, 0x55, 0x3a, 0x5f, 0x93, 0x7f, 0x00, 0xbb, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x3c,
+	0x67, 0x79, 0x6b, 0x25, 0x06, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -695,6 +802,7 @@ type AdminClient interface {
 	UpdateProject(ctx context.Context, in *UpdateProjectRequest, opts ...grpc.CallOption) (*UpdateProjectResponse, error)
 	ListDocuments(ctx context.Context, in *ListDocumentsRequest, opts ...grpc.CallOption) (*ListDocumentsResponse, error)
 	GetDocument(ctx context.Context, in *GetDocumentRequest, opts ...grpc.CallOption) (*GetDocumentResponse, error)
+	ListChanges(ctx context.Context, in *ListChangesRequest, opts ...grpc.CallOption) (*ListChangesResponse, error)
 }
 
 type adminClient struct {
@@ -759,6 +867,15 @@ func (c *adminClient) GetDocument(ctx context.Context, in *GetDocumentRequest, o
 	return out, nil
 }
 
+func (c *adminClient) ListChanges(ctx context.Context, in *ListChangesRequest, opts ...grpc.CallOption) (*ListChangesResponse, error) {
+	out := new(ListChangesResponse)
+	err := c.cc.Invoke(ctx, "/api.Admin/ListChanges", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // AdminServer is the server API for Admin service.
 type AdminServer interface {
 	CreateProject(context.Context, *CreateProjectRequest) (*CreateProjectResponse, error)
@@ -767,6 +884,7 @@ type AdminServer interface {
 	UpdateProject(context.Context, *UpdateProjectRequest) (*UpdateProjectResponse, error)
 	ListDocuments(context.Context, *ListDocumentsRequest) (*ListDocumentsResponse, error)
 	GetDocument(context.Context, *GetDocumentRequest) (*GetDocumentResponse, error)
+	ListChanges(context.Context, *ListChangesRequest) (*ListChangesResponse, error)
 }
 
 // UnimplementedAdminServer can be embedded to have forward compatible implementations.
@@ -791,6 +909,9 @@ func (*UnimplementedAdminServer) ListDocuments(ctx context.Context, req *ListDoc
 func (*UnimplementedAdminServer) GetDocument(ctx context.Context, req *GetDocumentRequest) (*GetDocumentResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method GetDocument not implemented")
 }
+func (*UnimplementedAdminServer) ListChanges(ctx context.Context, req *ListChangesRequest) (*ListChangesResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListChanges not implemented")
+}
 
 func RegisterAdminServer(s *grpc.Server, srv AdminServer) {
 	s.RegisterService(&_Admin_serviceDesc, srv)
@@ -904,6 +1025,24 @@ func _Admin_GetDocument_Handler(srv interface{}, ctx context.Context, dec func(i
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Admin_ListChanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ListChangesRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(AdminServer).ListChanges(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Admin/ListChanges",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(AdminServer).ListChanges(ctx, req.(*ListChangesRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Admin_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "api.Admin",
 	HandlerType: (*AdminServer)(nil),
@@ -932,6 +1071,10 @@ var _Admin_serviceDesc = grpc.ServiceDesc{
 			MethodName: "GetDocument",
 			Handler:    _Admin_GetDocument_Handler,
 		},
+		{
+			MethodName: "ListChanges",
+			Handler:    _Admin_ListChanges_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "admin.proto",
@@ -1413,6 +1556,88 @@ func (m *GetDocumentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *ListChangesRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ListChangesRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ListChangesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.DocumentKey) > 0 {
+		i -= len(m.DocumentKey)
+		copy(dAtA[i:], m.DocumentKey)
+		i = encodeVarintAdmin(dAtA, i, uint64(len(m.DocumentKey)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.ProjectName) > 0 {
+		i -= len(m.ProjectName)
+		copy(dAtA[i:], m.ProjectName)
+		i = encodeVarintAdmin(dAtA, i, uint64(len(m.ProjectName)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *ListChangesResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ListChangesResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ListChangesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Changes) > 0 {
+		for iNdEx := len(m.Changes) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Changes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintAdmin(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0xa
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintAdmin(dAtA []byte, offset int, v uint64) int {
 	offset -= sovAdmin(v)
 	base := offset
@@ -1634,6 +1859,44 @@ func (m *GetDocumentResponse) Size() (n int) {
 	return n
 }
 
+func (m *ListChangesRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.ProjectName)
+	if l > 0 {
+		n += 1 + l + sovAdmin(uint64(l))
+	}
+	l = len(m.DocumentKey)
+	if l > 0 {
+		n += 1 + l + sovAdmin(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *ListChangesResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.Changes) > 0 {
+		for _, e := range m.Changes {
+			l = e.Size()
+			n += 1 + l + sovAdmin(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func sovAdmin(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -2763,6 +3026,206 @@ func (m *GetDocumentResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ListChangesRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAdmin
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ListChangesRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ListChangesRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ProjectName", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAdmin
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ProjectName = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field DocumentKey", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAdmin
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.DocumentKey = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAdmin(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *ListChangesResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowAdmin
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ListChangesResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ListChangesResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Changes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowAdmin
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Changes = append(m.Changes, &Change{})
+			if err := m.Changes[len(m.Changes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipAdmin(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthAdmin
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipAdmin(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/api/admin.proto b/api/admin.proto
index a568dcb5..89dffc56 100644
--- a/api/admin.proto
+++ b/api/admin.proto
@@ -28,6 +28,8 @@ service Admin {
 
   rpc ListDocuments (ListDocumentsRequest) returns (ListDocumentsResponse) {}
   rpc GetDocument (GetDocumentRequest) returns (GetDocumentResponse) {}
+
+  rpc ListChanges (ListChangesRequest) returns (ListChangesResponse) {}
 }
 
 message CreateProjectRequest {
@@ -80,3 +82,12 @@ message GetDocumentRequest {
 message GetDocumentResponse {
   DocumentSummary document = 1;
 }
+
+message ListChangesRequest {
+  string project_name = 1;
+  string document_key = 2;
+}
+
+message ListChangesResponse {
+  repeated Change changes = 1;
+}
diff --git a/api/types/auth_webhook.go b/api/types/auth_webhook.go
index 044a729e..f7e0d46f 100644
--- a/api/types/auth_webhook.go
+++ b/api/types/auth_webhook.go
@@ -54,7 +54,6 @@ const (
 	DetachDocument   Method = "DetachDocument"
 	PushPull         Method = "PushPull"
 	WatchDocuments   Method = "WatchDocuments"
-	ListChanges      Method = "ListChangeSummaries"
 )
 
 // IsAuthMethod returns whether the given method can be used for authorization.
@@ -76,7 +75,6 @@ func AuthMethods() []Method {
 		DetachDocument,
 		PushPull,
 		WatchDocuments,
-		ListChanges,
 	}
 }
 
diff --git a/api/yorkie.pb.go b/api/yorkie.pb.go
index fac6c95c..2d118338 100644
--- a/api/yorkie.pb.go
+++ b/api/yorkie.pb.go
@@ -836,108 +836,6 @@ func (m *UpdatePresenceResponse) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_UpdatePresenceResponse proto.InternalMessageInfo
 
-type ListChangesRequest struct {
-	ClientId             []byte   `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
-	DocumentKey          string   `protobuf:"bytes,2,opt,name=document_key,json=documentKey,proto3" json:"document_key,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *ListChangesRequest) Reset()         { *m = ListChangesRequest{} }
-func (m *ListChangesRequest) String() string { return proto.CompactTextString(m) }
-func (*ListChangesRequest) ProtoMessage()    {}
-func (*ListChangesRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_b60f170c5b305914, []int{14}
-}
-func (m *ListChangesRequest) XXX_Unmarshal(b []byte) error {
-	return m.Unmarshal(b)
-}
-func (m *ListChangesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	if deterministic {
-		return xxx_messageInfo_ListChangesRequest.Marshal(b, m, deterministic)
-	} else {
-		b = b[:cap(b)]
-		n, err := m.MarshalToSizedBuffer(b)
-		if err != nil {
-			return nil, err
-		}
-		return b[:n], nil
-	}
-}
-func (m *ListChangesRequest) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ListChangesRequest.Merge(m, src)
-}
-func (m *ListChangesRequest) XXX_Size() int {
-	return m.Size()
-}
-func (m *ListChangesRequest) XXX_DiscardUnknown() {
-	xxx_messageInfo_ListChangesRequest.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ListChangesRequest proto.InternalMessageInfo
-
-func (m *ListChangesRequest) GetClientId() []byte {
-	if m != nil {
-		return m.ClientId
-	}
-	return nil
-}
-
-func (m *ListChangesRequest) GetDocumentKey() string {
-	if m != nil {
-		return m.DocumentKey
-	}
-	return ""
-}
-
-type ListChangesResponse struct {
-	Changes              []*Change `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
-	XXX_unrecognized     []byte    `json:"-"`
-	XXX_sizecache        int32     `json:"-"`
-}
-
-func (m *ListChangesResponse) Reset()         { *m = ListChangesResponse{} }
-func (m *ListChangesResponse) String() string { return proto.CompactTextString(m) }
-func (*ListChangesResponse) ProtoMessage()    {}
-func (*ListChangesResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_b60f170c5b305914, []int{15}
-}
-func (m *ListChangesResponse) XXX_Unmarshal(b []byte) error {
-	return m.Unmarshal(b)
-}
-func (m *ListChangesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	if deterministic {
-		return xxx_messageInfo_ListChangesResponse.Marshal(b, m, deterministic)
-	} else {
-		b = b[:cap(b)]
-		n, err := m.MarshalToSizedBuffer(b)
-		if err != nil {
-			return nil, err
-		}
-		return b[:n], nil
-	}
-}
-func (m *ListChangesResponse) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ListChangesResponse.Merge(m, src)
-}
-func (m *ListChangesResponse) XXX_Size() int {
-	return m.Size()
-}
-func (m *ListChangesResponse) XXX_DiscardUnknown() {
-	xxx_messageInfo_ListChangesResponse.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ListChangesResponse proto.InternalMessageInfo
-
-func (m *ListChangesResponse) GetChanges() []*Change {
-	if m != nil {
-		return m.Changes
-	}
-	return nil
-}
-
 func init() {
 	proto.RegisterType((*ActivateClientRequest)(nil), "api.ActivateClientRequest")
 	proto.RegisterType((*ActivateClientResponse)(nil), "api.ActivateClientResponse")
@@ -955,58 +853,52 @@ func init() {
 	proto.RegisterType((*PushPullResponse)(nil), "api.PushPullResponse")
 	proto.RegisterType((*UpdatePresenceRequest)(nil), "api.UpdatePresenceRequest")
 	proto.RegisterType((*UpdatePresenceResponse)(nil), "api.UpdatePresenceResponse")
-	proto.RegisterType((*ListChangesRequest)(nil), "api.ListChangesRequest")
-	proto.RegisterType((*ListChangesResponse)(nil), "api.ListChangesResponse")
 }
 
 func init() { proto.RegisterFile("yorkie.proto", fileDescriptor_b60f170c5b305914) }
 
 var fileDescriptor_b60f170c5b305914 = []byte{
-	// 703 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xcd, 0x6e, 0xd3, 0x40,
-	0x10, 0x8e, 0x93, 0x36, 0x34, 0xe3, 0x34, 0x8d, 0x16, 0x92, 0x5a, 0x2e, 0x8d, 0x82, 0xab, 0x4a,
-	0x11, 0x87, 0xa8, 0x0a, 0x52, 0xf9, 0x11, 0x1c, 0x9a, 0x06, 0xa9, 0x55, 0x28, 0x0a, 0x56, 0x11,
-	0xe2, 0x14, 0xb6, 0xce, 0xb6, 0x5d, 0x25, 0xb5, 0x8d, 0x77, 0x53, 0xc9, 0x3c, 0x09, 0x8f, 0xd4,
-	0x23, 0x2f, 0x80, 0x84, 0xca, 0x85, 0xc7, 0x40, 0xd9, 0x5d, 0xb7, 0xb1, 0xe3, 0x42, 0x40, 0x94,
-	0x9b, 0x33, 0xb3, 0xf3, 0x7d, 0xf3, 0xed, 0xcc, 0xce, 0x04, 0x8a, 0xa1, 0x17, 0x0c, 0x29, 0x69,
-	0xfa, 0x81, 0xc7, 0x3d, 0x94, 0xc3, 0x3e, 0x35, 0x57, 0x02, 0xc2, 0xbc, 0x71, 0xe0, 0x10, 0x26,
-	0xad, 0xd6, 0x36, 0x54, 0x76, 0x1c, 0x4e, 0xcf, 0x31, 0x27, 0xbb, 0x23, 0x4a, 0x5c, 0x6e, 0x93,
-	0x8f, 0x63, 0xc2, 0x38, 0x5a, 0x07, 0x70, 0x84, 0xa1, 0x3f, 0x24, 0xa1, 0xa1, 0xd5, 0xb5, 0x46,
-	0xc1, 0x2e, 0x48, 0x4b, 0x97, 0x84, 0xd6, 0x21, 0x54, 0x93, 0x71, 0xcc, 0xf7, 0x5c, 0x46, 0x7e,
-	0x13, 0x88, 0xd6, 0x40, 0xfd, 0xe8, 0xd3, 0x81, 0x91, 0xad, 0x6b, 0x8d, 0xa2, 0xbd, 0x24, 0x0d,
-	0xfb, 0x03, 0x6b, 0x1b, 0x56, 0x3b, 0x04, 0xa7, 0xe6, 0x13, 0x8b, 0xd3, 0x12, 0x71, 0x8f, 0xc1,
-	0x98, 0x8d, 0x53, 0xf9, 0xfc, 0x32, 0xf0, 0x18, 0x2a, 0x3b, 0x9c, 0x63, 0xe7, 0xb4, 0xe3, 0x39,
-	0xe3, 0xb3, 0x39, 0xe9, 0xd0, 0x16, 0xe8, 0xce, 0x29, 0x76, 0x4f, 0x48, 0xdf, 0xc7, 0xce, 0x50,
-	0xa8, 0xd0, 0x5b, 0x2b, 0x4d, 0xec, 0xd3, 0xe6, 0xae, 0xb0, 0xf7, 0xb0, 0x33, 0xb4, 0xc1, 0xb9,
-	0xfa, 0xb6, 0x4e, 0xa0, 0x9a, 0xe4, 0x99, 0x23, 0xbd, 0xbf, 0x20, 0x3a, 0x86, 0x4a, 0x87, 0xfc,
-	0x07, 0x41, 0x14, 0xaa, 0x49, 0x9e, 0xf9, 0xea, 0xff, 0xe7, 0x54, 0x18, 0x2a, 0xef, 0x30, 0xbf,
-	0x66, 0x62, 0x91, 0xa4, 0x0d, 0xc8, 0x4b, 0x5c, 0xc1, 0xa2, 0xb7, 0x74, 0x89, 0x22, 0xcb, 0xaf,
-	0x5c, 0x68, 0x03, 0x96, 0x07, 0x2a, 0x70, 0x92, 0x10, 0x33, 0xb2, 0xf5, 0x5c, 0xa3, 0x60, 0x17,
-	0x23, 0x63, 0x97, 0x84, 0xcc, 0xfa, 0x91, 0x85, 0x6a, 0x92, 0x43, 0xc9, 0x39, 0x84, 0x12, 0x75,
-	0x29, 0xa7, 0x78, 0x44, 0x3f, 0x61, 0x4e, 0x3d, 0x57, 0x91, 0x3d, 0x14, 0x64, 0xe9, 0x41, 0xcd,
-	0xfd, 0x58, 0xc4, 0x5e, 0xc6, 0x4e, 0x60, 0xa0, 0x4d, 0x58, 0x24, 0xe7, 0x93, 0xcc, 0xa5, 0xfe,
-	0x65, 0x01, 0xd6, 0xf1, 0x9c, 0x97, 0x13, 0xe3, 0x5e, 0xc6, 0x96, 0x5e, 0xf3, 0x42, 0x83, 0x52,
-	0x1c, 0x0b, 0x1d, 0x43, 0xd9, 0x27, 0x24, 0x60, 0xfd, 0x33, 0xec, 0xf7, 0x8f, 0xc2, 0xfe, 0xc0,
-	0x73, 0x0c, 0xad, 0x9e, 0x6b, 0xe8, 0xad, 0x17, 0xf3, 0x67, 0xd4, 0xec, 0x4d, 0x20, 0x0e, 0xb0,
-	0xdf, 0x0e, 0x27, 0xa4, 0x2e, 0x0f, 0x42, 0x7b, 0xd9, 0x9f, 0xb6, 0x99, 0xaf, 0x01, 0xcd, 0x1e,
-	0x42, 0x65, 0xc8, 0x5d, 0x57, 0x75, 0xf2, 0x89, 0x2c, 0x58, 0x3c, 0xc7, 0xa3, 0x31, 0x51, 0x4a,
-	0x8a, 0x53, 0x35, 0x60, 0xb6, 0x74, 0x3d, 0xcb, 0x3e, 0xd1, 0xda, 0x79, 0x58, 0x38, 0xf2, 0x06,
-	0xa1, 0xf5, 0x01, 0x56, 0x7a, 0x63, 0x76, 0xda, 0x1b, 0x8f, 0x46, 0xb7, 0xd4, 0x9a, 0x18, 0xca,
-	0xd7, 0x0c, 0xb7, 0xf3, 0xca, 0x30, 0x54, 0xde, 0xfa, 0x03, 0xcc, 0x49, 0x2f, 0x20, 0x8c, 0xb8,
-	0x0e, 0xf9, 0xf7, 0x2d, 0x69, 0x40, 0x35, 0x49, 0x21, 0xb5, 0x58, 0x87, 0x80, 0x5e, 0x51, 0xc6,
-	0x65, 0x6a, 0x6c, 0xae, 0x4b, 0x7c, 0x00, 0xc5, 0x69, 0x46, 0x21, 0xb1, 0x60, 0xeb, 0x53, 0x84,
-	0xd6, 0x73, 0xb8, 0x1b, 0x43, 0x55, 0x17, 0xb7, 0x09, 0x77, 0xa4, 0x6e, 0xa6, 0xba, 0x4c, 0x9f,
-	0xba, 0x17, 0x3b, 0xf2, 0xb5, 0xbe, 0x2e, 0x40, 0xfe, 0xbd, 0xd8, 0x36, 0xa8, 0x0b, 0xa5, 0xf8,
-	0x66, 0x40, 0xa6, 0x08, 0x49, 0x5d, 0x33, 0xe6, 0x5a, 0xaa, 0x4f, 0x29, 0xcd, 0xa0, 0x37, 0x50,
-	0x4e, 0x0e, 0x76, 0x74, 0x5f, 0x3e, 0x96, 0xf4, 0x3d, 0x61, 0xae, 0xdf, 0xe0, 0xbd, 0x82, 0xec,
-	0x42, 0x29, 0x7e, 0xb1, 0x2a, 0xbf, 0xd4, 0x82, 0xaa, 0xfc, 0x6e, 0xa8, 0x84, 0x00, 0x8b, 0xcf,
-	0xf5, 0x48, 0x6c, 0xda, 0x52, 0x89, 0xc4, 0xa6, 0x2e, 0x02, 0x09, 0x16, 0x9f, 0xa9, 0x0a, 0x2c,
-	0x75, 0xa0, 0x2b, 0xb0, 0xf4, 0x21, 0x6c, 0x65, 0xd0, 0x01, 0x94, 0xe2, 0xa3, 0x40, 0x81, 0xa5,
-	0x8e, 0x52, 0x05, 0x96, 0x3e, 0x3b, 0xac, 0xcc, 0x96, 0x86, 0x9e, 0xc2, 0x52, 0xf4, 0xa8, 0xd0,
-	0x3d, 0x71, 0x38, 0xf1, 0x8a, 0xcd, 0x4a, 0xc2, 0x7a, 0x95, 0x49, 0x1b, 0xf4, 0xa9, 0xce, 0x42,
-	0xab, 0xe2, 0xdc, 0x6c, 0x07, 0x9b, 0xc6, 0xac, 0x23, 0xc2, 0x68, 0x97, 0x2f, 0x2e, 0x6b, 0xda,
-	0x97, 0xcb, 0x9a, 0xf6, 0xed, 0xb2, 0xa6, 0x7d, 0xfe, 0x5e, 0xcb, 0x1c, 0xe5, 0xc5, 0xff, 0x97,
-	0x47, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb8, 0xda, 0xf3, 0xdb, 0xe5, 0x08, 0x00, 0x00,
+	// 639 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xd3, 0x30,
+	0x18, 0x6d, 0xda, 0xad, 0xda, 0xbe, 0x76, 0x5d, 0x65, 0xd1, 0x52, 0x65, 0xac, 0xaa, 0x32, 0x21,
+	0x55, 0x5c, 0x54, 0x53, 0x91, 0xc6, 0x8f, 0xc4, 0xc5, 0xb6, 0x22, 0x6d, 0xaa, 0x86, 0x4a, 0x34,
+	0x84, 0xb8, 0x0a, 0x9e, 0xe3, 0x6d, 0x56, 0xbb, 0x24, 0xc4, 0xce, 0xa4, 0xf0, 0x24, 0xf0, 0x46,
+	0xbb, 0xe4, 0x11, 0xd0, 0xb8, 0xe1, 0x31, 0x50, 0x62, 0x67, 0x5b, 0x32, 0x03, 0x05, 0x31, 0xee,
+	0xd2, 0xe3, 0x9c, 0x73, 0xbe, 0x53, 0xdb, 0x27, 0x50, 0x8f, 0xfd, 0x70, 0xca, 0xe8, 0x20, 0x08,
+	0x7d, 0xe1, 0xa3, 0x0a, 0x0e, 0x98, 0xb9, 0x1a, 0x52, 0xee, 0x47, 0x21, 0xa1, 0x5c, 0xa2, 0xd6,
+	0x16, 0xb4, 0xb6, 0x89, 0x60, 0xe7, 0x58, 0xd0, 0xdd, 0x19, 0xa3, 0x9e, 0xb0, 0xe9, 0x87, 0x88,
+	0x72, 0x81, 0xd6, 0x01, 0x48, 0x0a, 0x38, 0x53, 0x1a, 0x77, 0x8c, 0x9e, 0xd1, 0x5f, 0xb6, 0x97,
+	0x25, 0x32, 0xa6, 0xb1, 0x75, 0x08, 0xed, 0x22, 0x8f, 0x07, 0xbe, 0xc7, 0xe9, 0x6f, 0x88, 0x68,
+	0x0d, 0xd4, 0x0f, 0x87, 0xb9, 0x9d, 0x72, 0xcf, 0xe8, 0xd7, 0xed, 0x25, 0x09, 0xec, 0xbb, 0xd6,
+	0x16, 0xdc, 0x1f, 0x51, 0xac, 0x9d, 0x27, 0xc7, 0x33, 0x0a, 0xbc, 0x27, 0xd0, 0xb9, 0xcd, 0x53,
+	0xf3, 0xfc, 0x92, 0x78, 0x0c, 0xad, 0x6d, 0x21, 0x30, 0x39, 0x1d, 0xf9, 0x24, 0x3a, 0x9b, 0xd3,
+	0x0e, 0x6d, 0x42, 0x8d, 0x9c, 0x62, 0xef, 0x84, 0x3a, 0x01, 0x26, 0xd3, 0x34, 0x45, 0x6d, 0xb8,
+	0x3a, 0xc0, 0x01, 0x1b, 0xec, 0xa6, 0xf8, 0x04, 0x93, 0xa9, 0x0d, 0xe4, 0xea, 0xd9, 0x3a, 0x81,
+	0x76, 0xd1, 0x67, 0x8e, 0xf1, 0xfe, 0xc2, 0xe8, 0x18, 0x5a, 0x23, 0xfa, 0x1f, 0x02, 0x31, 0x68,
+	0x17, 0x7d, 0xe6, 0xdb, 0xff, 0x3f, 0xb7, 0xc2, 0xd0, 0x7a, 0x8b, 0xc5, 0xb5, 0x13, 0xcf, 0x22,
+	0x6d, 0x40, 0x55, 0xea, 0xa6, 0x2e, 0xb5, 0x61, 0x4d, 0xaa, 0xc8, 0xed, 0x57, 0x4b, 0x68, 0x03,
+	0x56, 0x5c, 0x45, 0x4c, 0x06, 0xe2, 0x9d, 0x72, 0xaf, 0xd2, 0x5f, 0xb6, 0xeb, 0x19, 0x38, 0xa6,
+	0x31, 0xb7, 0xbe, 0x97, 0xa1, 0x5d, 0xf4, 0x50, 0x71, 0x0e, 0xa1, 0xc1, 0x3c, 0x26, 0x18, 0x9e,
+	0xb1, 0x8f, 0x58, 0x30, 0xdf, 0x53, 0x66, 0x8f, 0x52, 0x33, 0x3d, 0x69, 0xb0, 0x9f, 0x63, 0xec,
+	0x95, 0xec, 0x82, 0x06, 0x7a, 0x08, 0x8b, 0xf4, 0x3c, 0x99, 0x5c, 0xe6, 0x5f, 0x49, 0xc5, 0x46,
+	0x3e, 0x79, 0x99, 0x80, 0x7b, 0x25, 0x5b, 0xae, 0x9a, 0x17, 0x06, 0x34, 0xf2, 0x5a, 0xe8, 0x18,
+	0x9a, 0x01, 0xa5, 0x21, 0x77, 0xce, 0x70, 0xe0, 0x1c, 0xc5, 0x8e, 0xeb, 0x93, 0x8e, 0xd1, 0xab,
+	0xf4, 0x6b, 0xc3, 0x17, 0xf3, 0x4f, 0x34, 0x98, 0x24, 0x12, 0x07, 0x38, 0xd8, 0x89, 0x13, 0x53,
+	0x4f, 0x84, 0xb1, 0xbd, 0x12, 0xdc, 0xc4, 0xcc, 0x57, 0x80, 0x6e, 0xbf, 0x84, 0x9a, 0x50, 0xb9,
+	0xde, 0xd5, 0xe4, 0x11, 0x59, 0xb0, 0x78, 0x8e, 0x67, 0x11, 0x55, 0x49, 0xea, 0x37, 0xf6, 0x80,
+	0xdb, 0x72, 0xe9, 0x79, 0xf9, 0xa9, 0xb1, 0x53, 0x85, 0x85, 0x23, 0xdf, 0x8d, 0xad, 0xf7, 0xb0,
+	0x3a, 0x89, 0xf8, 0xe9, 0x24, 0x9a, 0xcd, 0xee, 0xe8, 0x68, 0x62, 0x68, 0x5e, 0x3b, 0xdc, 0xcd,
+	0x2d, 0xc3, 0xd0, 0x7a, 0x13, 0xb8, 0x58, 0xd0, 0x49, 0x48, 0x39, 0xf5, 0x08, 0xfd, 0xf7, 0x47,
+	0xb2, 0x03, 0xed, 0xa2, 0x85, 0xcc, 0x32, 0xfc, 0xbc, 0x00, 0xd5, 0x77, 0x69, 0xb3, 0xa3, 0x31,
+	0x34, 0xf2, 0x2d, 0x8c, 0xcc, 0xd4, 0x50, 0x5b, 0xe9, 0xe6, 0x9a, 0x76, 0x4d, 0xaa, 0x5a, 0x25,
+	0xf4, 0x1a, 0x9a, 0xc5, 0x12, 0x45, 0x0f, 0xe4, 0xc1, 0xd4, 0x77, 0xb2, 0xb9, 0xfe, 0x93, 0xd5,
+	0x2b, 0xc9, 0x31, 0x34, 0xf2, 0x21, 0xd4, 0x7c, 0xda, 0x3f, 0x4f, 0xcd, 0xa7, 0x4f, 0x2d, 0xc5,
+	0xf2, 0x1d, 0x9a, 0x85, 0xd5, 0x15, 0x78, 0x16, 0x56, 0x5b, 0xba, 0x52, 0x2c, 0xdf, 0x5f, 0x4a,
+	0x4c, 0x5b, 0x9e, 0x4a, 0x4c, 0x5f, 0x78, 0x56, 0x09, 0x1d, 0x40, 0x23, 0x7f, 0xed, 0x94, 0x98,
+	0xb6, 0xb6, 0x94, 0x98, 0xfe, 0x9e, 0x5a, 0xa5, 0x4d, 0x03, 0x3d, 0x83, 0xa5, 0xec, 0x00, 0xa3,
+	0x7b, 0xe9, 0xcb, 0x85, 0x1b, 0x63, 0xb6, 0x0a, 0x68, 0x46, 0xde, 0x69, 0x5e, 0x5c, 0x76, 0x8d,
+	0x2f, 0x97, 0x5d, 0xe3, 0xeb, 0x65, 0xd7, 0xf8, 0xf4, 0xad, 0x5b, 0x3a, 0xaa, 0xa6, 0xdf, 0xf9,
+	0xc7, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4e, 0xf7, 0xc4, 0xde, 0x0d, 0x08, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -1028,7 +920,6 @@ type YorkieClient interface {
 	DetachDocument(ctx context.Context, in *DetachDocumentRequest, opts ...grpc.CallOption) (*DetachDocumentResponse, error)
 	WatchDocuments(ctx context.Context, in *WatchDocumentsRequest, opts ...grpc.CallOption) (Yorkie_WatchDocumentsClient, error)
 	PushPull(ctx context.Context, in *PushPullRequest, opts ...grpc.CallOption) (*PushPullResponse, error)
-	ListChanges(ctx context.Context, in *ListChangesRequest, opts ...grpc.CallOption) (*ListChangesResponse, error)
 }
 
 type yorkieClient struct {
@@ -1125,15 +1016,6 @@ func (c *yorkieClient) PushPull(ctx context.Context, in *PushPullRequest, opts .
 	return out, nil
 }
 
-func (c *yorkieClient) ListChanges(ctx context.Context, in *ListChangesRequest, opts ...grpc.CallOption) (*ListChangesResponse, error) {
-	out := new(ListChangesResponse)
-	err := c.cc.Invoke(ctx, "/api.Yorkie/ListChanges", in, out, opts...)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
 // YorkieServer is the server API for Yorkie service.
 type YorkieServer interface {
 	ActivateClient(context.Context, *ActivateClientRequest) (*ActivateClientResponse, error)
@@ -1143,7 +1025,6 @@ type YorkieServer interface {
 	DetachDocument(context.Context, *DetachDocumentRequest) (*DetachDocumentResponse, error)
 	WatchDocuments(*WatchDocumentsRequest, Yorkie_WatchDocumentsServer) error
 	PushPull(context.Context, *PushPullRequest) (*PushPullResponse, error)
-	ListChanges(context.Context, *ListChangesRequest) (*ListChangesResponse, error)
 }
 
 // UnimplementedYorkieServer can be embedded to have forward compatible implementations.
@@ -1171,9 +1052,6 @@ func (*UnimplementedYorkieServer) WatchDocuments(req *WatchDocumentsRequest, srv
 func (*UnimplementedYorkieServer) PushPull(ctx context.Context, req *PushPullRequest) (*PushPullResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method PushPull not implemented")
 }
-func (*UnimplementedYorkieServer) ListChanges(ctx context.Context, req *ListChangesRequest) (*ListChangesResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method ListChanges not implemented")
-}
 
 func RegisterYorkieServer(s *grpc.Server, srv YorkieServer) {
 	s.RegisterService(&_Yorkie_serviceDesc, srv)
@@ -1308,24 +1186,6 @@ func _Yorkie_PushPull_Handler(srv interface{}, ctx context.Context, dec func(int
 	return interceptor(ctx, in, info, handler)
 }
 
-func _Yorkie_ListChanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(ListChangesRequest)
-	if err := dec(in); err != nil {
-		return nil, err
-	}
-	if interceptor == nil {
-		return srv.(YorkieServer).ListChanges(ctx, in)
-	}
-	info := &grpc.UnaryServerInfo{
-		Server:     srv,
-		FullMethod: "/api.Yorkie/ListChanges",
-	}
-	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(YorkieServer).ListChanges(ctx, req.(*ListChangesRequest))
-	}
-	return interceptor(ctx, in, info, handler)
-}
-
 var _Yorkie_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "api.Yorkie",
 	HandlerType: (*YorkieServer)(nil),
@@ -1354,10 +1214,6 @@ var _Yorkie_serviceDesc = grpc.ServiceDesc{
 			MethodName: "PushPull",
 			Handler:    _Yorkie_PushPull_Handler,
 		},
-		{
-			MethodName: "ListChanges",
-			Handler:    _Yorkie_ListChanges_Handler,
-		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
@@ -2042,88 +1898,6 @@ func (m *UpdatePresenceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error)
 	return len(dAtA) - i, nil
 }
 
-func (m *ListChangesRequest) Marshal() (dAtA []byte, err error) {
-	size := m.Size()
-	dAtA = make([]byte, size)
-	n, err := m.MarshalToSizedBuffer(dAtA[:size])
-	if err != nil {
-		return nil, err
-	}
-	return dAtA[:n], nil
-}
-
-func (m *ListChangesRequest) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
-
-func (m *ListChangesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	if m.XXX_unrecognized != nil {
-		i -= len(m.XXX_unrecognized)
-		copy(dAtA[i:], m.XXX_unrecognized)
-	}
-	if len(m.DocumentKey) > 0 {
-		i -= len(m.DocumentKey)
-		copy(dAtA[i:], m.DocumentKey)
-		i = encodeVarintYorkie(dAtA, i, uint64(len(m.DocumentKey)))
-		i--
-		dAtA[i] = 0x12
-	}
-	if len(m.ClientId) > 0 {
-		i -= len(m.ClientId)
-		copy(dAtA[i:], m.ClientId)
-		i = encodeVarintYorkie(dAtA, i, uint64(len(m.ClientId)))
-		i--
-		dAtA[i] = 0xa
-	}
-	return len(dAtA) - i, nil
-}
-
-func (m *ListChangesResponse) Marshal() (dAtA []byte, err error) {
-	size := m.Size()
-	dAtA = make([]byte, size)
-	n, err := m.MarshalToSizedBuffer(dAtA[:size])
-	if err != nil {
-		return nil, err
-	}
-	return dAtA[:n], nil
-}
-
-func (m *ListChangesResponse) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
-
-func (m *ListChangesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	if m.XXX_unrecognized != nil {
-		i -= len(m.XXX_unrecognized)
-		copy(dAtA[i:], m.XXX_unrecognized)
-	}
-	if len(m.Changes) > 0 {
-		for iNdEx := len(m.Changes) - 1; iNdEx >= 0; iNdEx-- {
-			{
-				size, err := m.Changes[iNdEx].MarshalToSizedBuffer(dAtA[:i])
-				if err != nil {
-					return 0, err
-				}
-				i -= size
-				i = encodeVarintYorkie(dAtA, i, uint64(size))
-			}
-			i--
-			dAtA[i] = 0xa
-		}
-	}
-	return len(dAtA) - i, nil
-}
-
 func encodeVarintYorkie(dAtA []byte, offset int, v uint64) int {
 	offset -= sovYorkie(v)
 	base := offset
@@ -2443,44 +2217,6 @@ func (m *UpdatePresenceResponse) Size() (n int) {
 	return n
 }
 
-func (m *ListChangesRequest) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	l = len(m.ClientId)
-	if l > 0 {
-		n += 1 + l + sovYorkie(uint64(l))
-	}
-	l = len(m.DocumentKey)
-	if l > 0 {
-		n += 1 + l + sovYorkie(uint64(l))
-	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
-
-func (m *ListChangesResponse) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if len(m.Changes) > 0 {
-		for _, e := range m.Changes {
-			l = e.Size()
-			n += 1 + l + sovYorkie(uint64(l))
-		}
-	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
-
 func sovYorkie(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -4171,208 +3907,6 @@ func (m *UpdatePresenceResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
-func (m *ListChangesRequest) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
-		preIndex := iNdEx
-		var wire uint64
-		for shift := uint(0); ; shift += 7 {
-			if shift >= 64 {
-				return ErrIntOverflowYorkie
-			}
-			if iNdEx >= l {
-				return io.ErrUnexpectedEOF
-			}
-			b := dAtA[iNdEx]
-			iNdEx++
-			wire |= uint64(b&0x7F) << shift
-			if b < 0x80 {
-				break
-			}
-		}
-		fieldNum := int32(wire >> 3)
-		wireType := int(wire & 0x7)
-		if wireType == 4 {
-			return fmt.Errorf("proto: ListChangesRequest: wiretype end group for non-group")
-		}
-		if fieldNum <= 0 {
-			return fmt.Errorf("proto: ListChangesRequest: illegal tag %d (wire type %d)", fieldNum, wire)
-		}
-		switch fieldNum {
-		case 1:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType)
-			}
-			var byteLen int
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowYorkie
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				byteLen |= int(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			if byteLen < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			postIndex := iNdEx + byteLen
-			if postIndex < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.ClientId = append(m.ClientId[:0], dAtA[iNdEx:postIndex]...)
-			if m.ClientId == nil {
-				m.ClientId = []byte{}
-			}
-			iNdEx = postIndex
-		case 2:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field DocumentKey", wireType)
-			}
-			var stringLen uint64
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowYorkie
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.DocumentKey = string(dAtA[iNdEx:postIndex])
-			iNdEx = postIndex
-		default:
-			iNdEx = preIndex
-			skippy, err := skipYorkie(dAtA[iNdEx:])
-			if err != nil {
-				return err
-			}
-			if (skippy < 0) || (iNdEx+skippy) < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			if (iNdEx + skippy) > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
-			iNdEx += skippy
-		}
-	}
-
-	if iNdEx > l {
-		return io.ErrUnexpectedEOF
-	}
-	return nil
-}
-func (m *ListChangesResponse) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
-		preIndex := iNdEx
-		var wire uint64
-		for shift := uint(0); ; shift += 7 {
-			if shift >= 64 {
-				return ErrIntOverflowYorkie
-			}
-			if iNdEx >= l {
-				return io.ErrUnexpectedEOF
-			}
-			b := dAtA[iNdEx]
-			iNdEx++
-			wire |= uint64(b&0x7F) << shift
-			if b < 0x80 {
-				break
-			}
-		}
-		fieldNum := int32(wire >> 3)
-		wireType := int(wire & 0x7)
-		if wireType == 4 {
-			return fmt.Errorf("proto: ListChangesResponse: wiretype end group for non-group")
-		}
-		if fieldNum <= 0 {
-			return fmt.Errorf("proto: ListChangesResponse: illegal tag %d (wire type %d)", fieldNum, wire)
-		}
-		switch fieldNum {
-		case 1:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Changes", wireType)
-			}
-			var msglen int
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowYorkie
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				msglen |= int(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			if msglen < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			postIndex := iNdEx + msglen
-			if postIndex < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.Changes = append(m.Changes, &Change{})
-			if err := m.Changes[len(m.Changes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
-				return err
-			}
-			iNdEx = postIndex
-		default:
-			iNdEx = preIndex
-			skippy, err := skipYorkie(dAtA[iNdEx:])
-			if err != nil {
-				return err
-			}
-			if (skippy < 0) || (iNdEx+skippy) < 0 {
-				return ErrInvalidLengthYorkie
-			}
-			if (iNdEx + skippy) > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
-			iNdEx += skippy
-		}
-	}
-
-	if iNdEx > l {
-		return io.ErrUnexpectedEOF
-	}
-	return nil
-}
 func skipYorkie(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/api/yorkie.proto b/api/yorkie.proto
index d7c6d0fc..44c57e5a 100644
--- a/api/yorkie.proto
+++ b/api/yorkie.proto
@@ -30,8 +30,6 @@ service Yorkie {
   rpc DetachDocument (DetachDocumentRequest) returns (DetachDocumentResponse) {}
   rpc WatchDocuments (WatchDocumentsRequest) returns (stream WatchDocumentsResponse) {}
   rpc PushPull (PushPullRequest) returns (PushPullResponse) {}
-
-  rpc ListChanges (ListChangesRequest) returns (ListChangesResponse) {}
 }
 
 message ActivateClientRequest {
@@ -103,12 +101,3 @@ message UpdatePresenceRequest {
 }
 
 message UpdatePresenceResponse {}
-
-message ListChangesRequest {
-  bytes client_id = 1;
-  string document_key = 2;
-}
-
-message ListChangesResponse {
-  repeated Change changes = 1;
-}
diff --git a/client/client.go b/client/client.go
index a1677452..fc98c2ce 100644
--- a/client/client.go
+++ b/client/client.go
@@ -483,40 +483,6 @@ func (c *Client) UpdatePresence(ctx context.Context, k, v string) error {
 	return nil
 }
 
-// ListChangeSummaries returns the change summaries of the given document.
-func (c *Client) ListChangeSummaries(ctx context.Context, key key.Key) ([]*types.ChangeSummary, error) {
-	resp, err := c.client.ListChanges(ctx, &api.ListChangesRequest{
-		ClientId:    c.id.Bytes(),
-		DocumentKey: key.String(),
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	changes, err := converter.FromChanges(resp.Changes)
-	if err != nil {
-		return nil, err
-	}
-
-	doc := document.NewInternalDocument(key)
-
-	var summaries []*types.ChangeSummary
-	for _, c := range changes {
-		if err := doc.ApplyChanges(c); err != nil {
-			return nil, err
-		}
-
-		// TODO(hackerwins): doc.Marshal is expensive function. We need to optimize it.
-		summaries = append(summaries, &types.ChangeSummary{
-			ID:       c.ID(),
-			Message:  c.Message(),
-			Snapshot: doc.Marshal(),
-		})
-	}
-
-	return summaries, nil
-}
-
 // ID returns the ID of this client.
 func (c *Client) ID() *time.ActorID {
 	return c.id
diff --git a/cmd/yorkie/history.go b/cmd/yorkie/history.go
new file mode 100644
index 00000000..29c7eec3
--- /dev/null
+++ b/cmd/yorkie/history.go
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+	"context"
+	"errors"
+
+	"github.com/jedib0t/go-pretty/v6/table"
+	"github.com/spf13/cobra"
+
+	"github.com/yorkie-team/yorkie/admin"
+	"github.com/yorkie-team/yorkie/pkg/document/key"
+)
+
+func newHistoryCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "history [project name] [document key]",
+		Short: "Show the history of a document",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if len(args) != 2 {
+				return errors.New("project name and document key are required")
+			}
+
+			// TODO(hackerwins): use adminAddr from env or addr flag.
+			cli, err := admin.Dial("localhost:11103")
+			if err != nil {
+				return err
+			}
+			defer func() {
+				_ = cli.Close()
+			}()
+
+			ctx := context.Background()
+			changes, err := cli.ListChangeSummaries(ctx, args[0], key.Key(args[1]))
+			if err != nil {
+				return err
+			}
+
+			tw := table.NewWriter()
+			tw.Style().Options.DrawBorder = false
+			tw.Style().Options.SeparateColumns = false
+			tw.Style().Options.SeparateFooter = false
+			tw.Style().Options.SeparateHeader = false
+			tw.Style().Options.SeparateRows = false
+			tw.AppendHeader(table.Row{
+				"SEQ",
+				"MESSAGE",
+				"SNAPSHOT",
+			})
+			for _, change := range changes {
+				tw.AppendRow(table.Row{
+					change.ID.ServerSeq(),
+					change.Message,
+					change.Snapshot,
+				})
+			}
+			cmd.Printf("%s\n", tw.Render())
+			return nil
+		},
+	}
+}
+
+func init() {
+	rootCmd.AddCommand(newHistoryCmd())
+}
diff --git a/server/admin/server.go b/server/admin/server.go
index 43091dce..a1a74939 100644
--- a/server/admin/server.go
+++ b/server/admin/server.go
@@ -34,6 +34,7 @@ import (
 	"github.com/yorkie-team/yorkie/server/documents"
 	"github.com/yorkie-team/yorkie/server/grpchelper"
 	"github.com/yorkie-team/yorkie/server/logging"
+	"github.com/yorkie-team/yorkie/server/packs"
 	"github.com/yorkie-team/yorkie/server/projects"
 )
 
@@ -288,3 +289,43 @@ func (s *Server) ListDocuments(
 		Documents: pbDocuments,
 	}, nil
 }
+
+// ListChanges lists of changes for the given document.
+func (s *Server) ListChanges(
+	ctx context.Context,
+	req *api.ListChangesRequest,
+) (*api.ListChangesResponse, error) {
+	project, err := projects.GetProject(ctx, s.backend, req.ProjectName)
+	if err != nil {
+		return nil, err
+	}
+	docKey := key.Key(req.DocumentKey)
+
+	docInfo, err := documents.FindDocInfoByKey(
+		ctx,
+		s.backend,
+		project,
+		docKey,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	changes, err := packs.FindAllChanges(
+		ctx,
+		s.backend,
+		docInfo,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	pbChanges, err := converter.ToChanges(changes)
+	if err != nil {
+		return nil, err
+	}
+
+	return &api.ListChangesResponse{
+		Changes: pbChanges,
+	}, nil
+}
diff --git a/server/backend/database/database.go b/server/backend/database/database.go
index f1d9f5b3..71e05f45 100644
--- a/server/backend/database/database.go
+++ b/server/backend/database/database.go
@@ -94,10 +94,17 @@ type Database interface {
 		candidatesLimit int,
 	) ([]*ClientInfo, error)
 
-	// FindDocInfoByKey finds the document of the given key. If the
+	// FindDocInfoByKey finds the document of the given key.
+	FindDocInfoByKey(
+		ctx context.Context,
+		projectID types.ID,
+		docKey key.Key,
+	) (*DocInfo, error)
+
+	// FindDocInfoByKeyAndOwner finds the document of the given key. If the
 	// createDocIfNotExist condition is true, create the document if it does not
 	// exist.
-	FindDocInfoByKey(
+	FindDocInfoByKeyAndOwner(
 		ctx context.Context,
 		projectID types.ID,
 		clientID types.ID,
diff --git a/server/backend/database/memory/database.go b/server/backend/database/memory/database.go
index 9241f19a..b9c650ec 100644
--- a/server/backend/database/memory/database.go
+++ b/server/backend/database/memory/database.go
@@ -409,8 +409,10 @@ func (d *DB) FindDeactivateCandidates(
 	return infos, nil
 }
 
-// FindDocInfoByKey finds a docInfo by key.
-func (d *DB) FindDocInfoByKey(
+// FindDocInfoByKeyAndOwner finds the document of the given key. If the
+// createDocIfNotExist condition is true, create the document if it does not
+// exist.
+func (d *DB) FindDocInfoByKeyAndOwner(
 	ctx context.Context,
 	projectID types.ID,
 	clientID types.ID,
@@ -451,6 +453,26 @@ func (d *DB) FindDocInfoByKey(
 	return docInfo.DeepCopy(), nil
 }
 
+// FindDocInfoByKey finds the document of the given key.
+func (d *DB) FindDocInfoByKey(
+	ctx context.Context,
+	projectID types.ID,
+	key key.Key,
+) (*database.DocInfo, error) {
+	txn := d.db.Txn(false)
+	defer txn.Abort()
+
+	raw, err := txn.First(tblDocuments, "project_id_key", projectID.String(), key.String())
+	if err != nil {
+		return nil, err
+	}
+	if raw == nil {
+		return nil, fmt.Errorf("%s: %w", key, database.ErrDocumentNotFound)
+	}
+
+	return raw.(*database.DocInfo).DeepCopy(), nil
+}
+
 // FindDocInfoByID finds a docInfo of the given ID.
 func (d *DB) FindDocInfoByID(
 	ctx context.Context,
diff --git a/server/backend/database/mongo/client.go b/server/backend/database/mongo/client.go
index 5172cfb5..4510a8de 100644
--- a/server/backend/database/mongo/client.go
+++ b/server/backend/database/mongo/client.go
@@ -450,10 +450,10 @@ func (c *Client) FindDeactivateCandidates(
 	return clientInfos, nil
 }
 
-// FindDocInfoByKey finds the document of the given key. If the
+// FindDocInfoByKeyAndOwner finds the document of the given key. If the
 // createDocIfNotExist condition is true, create the document if it does not
 // exist.
-func (c *Client) FindDocInfoByKey(
+func (c *Client) FindDocInfoByKeyAndOwner(
 	ctx context.Context,
 	projectID types.ID,
 	clientID types.ID,
@@ -516,6 +516,37 @@ func (c *Client) FindDocInfoByKey(
 	return &docInfo, nil
 }
 
+// FindDocInfoByKey finds the document of the given key.
+func (c *Client) FindDocInfoByKey(
+	ctx context.Context,
+	projectID types.ID,
+	docKey key.Key,
+) (*database.DocInfo, error) {
+	encodedProjectID, err := encodeID(projectID)
+	if err != nil {
+		return nil, err
+	}
+
+	result := c.collection(colDocuments).FindOne(ctx, bson.M{
+		"project_id": encodedProjectID,
+		"key":        docKey,
+	})
+	if result.Err() == mongo.ErrNoDocuments {
+		return nil, fmt.Errorf("%s %s: %w", projectID, docKey, database.ErrDocumentNotFound)
+	}
+	if result.Err() != nil {
+		logging.From(ctx).Error(result.Err())
+		return nil, result.Err()
+	}
+
+	docInfo := database.DocInfo{}
+	if err := result.Decode(&docInfo); err != nil {
+		return nil, err
+	}
+
+	return &docInfo, nil
+}
+
 // FindDocInfoByID finds a docInfo of the given ID.
 func (c *Client) FindDocInfoByID(ctx context.Context, id types.ID) (*database.DocInfo, error) {
 	encodedDocID, err := encodeID(id)
diff --git a/server/documents/documents.go b/server/documents/documents.go
index a6b6114c..b01a915d 100644
--- a/server/documents/documents.go
+++ b/server/documents/documents.go
@@ -60,8 +60,8 @@ func GetDocumentSummary(
 	project *types.Project,
 	k key.Key,
 ) (*types.DocumentSummary, error) {
-	// TODO(hackerwins): Split FindDocInfoByKey into upsert and find.
-	docInfo, err := be.DB.FindDocInfoByKey(
+	// TODO(hackerwins): Split FindDocInfoByKeyAndOwner into upsert and find.
+	docInfo, err := be.DB.FindDocInfoByKeyAndOwner(
 		ctx,
 		project.ID,
 		types.IDFromActorID(time.InitialActorID),
@@ -87,8 +87,22 @@ func GetDocumentSummary(
 	}, nil
 }
 
-// FindDocInfo returns a document for the given document key and owner.
-func FindDocInfo(
+// FindDocInfoByKey returns a document for the given document key.
+func FindDocInfoByKey(
+	ctx context.Context,
+	be *backend.Backend,
+	project *types.Project,
+	docKey key.Key,
+) (*database.DocInfo, error) {
+	return be.DB.FindDocInfoByKey(
+		ctx,
+		project.ID,
+		docKey,
+	)
+}
+
+// FindDocInfoByKeyAndOwner returns a document for the given document key and owner.
+func FindDocInfoByKeyAndOwner(
 	ctx context.Context,
 	be *backend.Backend,
 	project *types.Project,
@@ -96,15 +110,11 @@ func FindDocInfo(
 	docKey key.Key,
 	createDocIfNotExist bool,
 ) (*database.DocInfo, error) {
-	docInfo, err := be.DB.FindDocInfoByKey(
+	return be.DB.FindDocInfoByKeyAndOwner(
 		ctx,
 		project.ID,
 		clientInfo.ID,
 		docKey,
 		createDocIfNotExist,
 	)
-	if err != nil {
-		return nil, err
-	}
-	return docInfo, nil
 }
diff --git a/server/rpc/yorkie_server.go b/server/rpc/yorkie_server.go
index 5dfc8ab5..4bd35bd3 100644
--- a/server/rpc/yorkie_server.go
+++ b/server/rpc/yorkie_server.go
@@ -164,7 +164,7 @@ func (s *yorkieServer) AttachDocument(
 	if err != nil {
 		return nil, err
 	}
-	docInfo, err := documents.FindDocInfo(
+	docInfo, err := documents.FindDocInfoByKeyAndOwner(
 		ctx,
 		s.backend,
 		projects.From(ctx),
@@ -245,7 +245,7 @@ func (s *yorkieServer) DetachDocument(
 	if err != nil {
 		return nil, err
 	}
-	docInfo, err := documents.FindDocInfo(
+	docInfo, err := documents.FindDocInfoByKeyAndOwner(
 		ctx,
 		s.backend,
 		projects.From(ctx),
@@ -331,7 +331,7 @@ func (s *yorkieServer) PushPull(
 	if err != nil {
 		return nil, err
 	}
-	docInfo, err := documents.FindDocInfo(
+	docInfo, err := documents.FindDocInfoByKeyAndOwner(
 		ctx,
 		s.backend,
 		projects.From(ctx),
@@ -472,67 +472,6 @@ func (s *yorkieServer) UpdatePresence(
 	return &api.UpdatePresenceResponse{}, nil
 }
 
-// ListChanges lists of changes for the given document.
-func (s *yorkieServer) ListChanges(
-	ctx context.Context,
-	req *api.ListChangesRequest,
-) (*api.ListChangesResponse, error) {
-	actorID, err := time.ActorIDFromBytes(req.ClientId)
-	if err != nil {
-		return nil, err
-	}
-	docKey := key.Key(req.DocumentKey)
-
-	if err := auth.VerifyAccess(ctx, s.backend, &types.AccessInfo{
-		Method: types.ListChanges,
-		Attributes: []types.AccessAttribute{{
-			Key:  docKey.String(),
-			Verb: types.Read,
-		}},
-	}); err != nil {
-		return nil, err
-	}
-
-	clientInfo, err := clients.FindClientInfo(
-		ctx,
-		s.backend.DB,
-		projects.From(ctx),
-		actorID,
-	)
-	if err != nil {
-		return nil, err
-	}
-	docInfo, err := documents.FindDocInfo(
-		ctx,
-		s.backend,
-		projects.From(ctx),
-		clientInfo,
-		docKey,
-		false,
-	)
-	if err != nil {
-		return nil, err
-	}
-
-	changes, err := packs.FindAllChanges(
-		ctx,
-		s.backend,
-		docInfo,
-	)
-	if err != nil {
-		return nil, err
-	}
-
-	pbChanges, err := converter.ToChanges(changes)
-	if err != nil {
-		return nil, err
-	}
-
-	return &api.ListChangesResponse{
-		Changes: pbChanges,
-	}, nil
-}
-
 func (s *yorkieServer) watchDocs(
 	ctx context.Context,
 	client types.Client,


Test patch:
diff --git a/server/backend/database/memory/database_test.go b/server/backend/database/memory/database_test.go
index 56e4fb54..850b6c50 100644
--- a/server/backend/database/memory/database_test.go
+++ b/server/backend/database/memory/database_test.go
@@ -98,10 +98,10 @@ func TestDB(t *testing.T) {
 		assert.NoError(t, err)
 
 		docKey := key.Key(fmt.Sprintf("tests$%s", t.Name()))
-		_, err = db.FindDocInfoByKey(ctx, projectID, clientInfo.ID, docKey, false)
+		_, err = db.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, docKey, false)
 		assert.ErrorIs(t, err, database.ErrDocumentNotFound)
 
-		docInfo, err := db.FindDocInfoByKey(ctx, projectID, clientInfo.ID, docKey, true)
+		docInfo, err := db.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, docKey, true)
 		assert.NoError(t, err)
 		assert.Equal(t, docKey, docInfo.Key)
 	})
@@ -111,7 +111,7 @@ func TestDB(t *testing.T) {
 		assert.NoError(t, err)
 
 		docKey := key.Key(fmt.Sprintf("tests$%s", t.Name()))
-		docInfo, err := db.FindDocInfoByKey(ctx, projectID, clientInfo.ID, docKey, true)
+		docInfo, err := db.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, docKey, true)
 		assert.NoError(t, err)
 
 		err = db.UpdateClientInfoAfterPushPull(ctx, clientInfo, docInfo)
@@ -124,7 +124,7 @@ func TestDB(t *testing.T) {
 		docKey := key.Key(fmt.Sprintf("tests$%s", t.Name()))
 
 		clientInfo, _ := db.ActivateClient(ctx, projectID, t.Name())
-		docInfo, _ := db.FindDocInfoByKey(ctx, projectID, clientInfo.ID, docKey, true)
+		docInfo, _ := db.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, docKey, true)
 		assert.NoError(t, clientInfo.AttachDocument(docInfo.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, clientInfo, docInfo))
 
@@ -169,7 +169,7 @@ func TestDB(t *testing.T) {
 		clientInfo, _ := db.ActivateClient(ctx, projectID, t.Name())
 		bytesID, _ := clientInfo.ID.Bytes()
 		actorID, _ := time.ActorIDFromBytes(bytesID)
-		docInfo, _ := db.FindDocInfoByKey(ctx, projectID, clientInfo.ID, docKey, true)
+		docInfo, _ := db.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, docKey, true)
 
 		doc := document.New(key.Key(t.Name()))
 		doc.SetActor(actorID)
@@ -220,7 +220,7 @@ func TestDB(t *testing.T) {
 		totalSize := 9
 		clientInfo, _ := localDB.ActivateClient(ctx, projectID, t.Name())
 		for i := 0; i < totalSize; i++ {
-			_, err := localDB.FindDocInfoByKey(ctx, projectID, clientInfo.ID, key.Key(fmt.Sprintf("%d", i)), true)
+			_, err := localDB.FindDocInfoByKeyAndOwner(ctx, projectID, clientInfo.ID, key.Key(fmt.Sprintf("%d", i)), true)
 			assert.NoError(t, err)
 		}
 
diff --git a/test/integration/history_test.go b/test/integration/history_test.go
index 668145a6..6074afc1 100644
--- a/test/integration/history_test.go
+++ b/test/integration/history_test.go
@@ -24,22 +24,27 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/yorkie-team/yorkie/admin"
 	"github.com/yorkie-team/yorkie/pkg/document"
 	"github.com/yorkie-team/yorkie/pkg/document/key"
 	"github.com/yorkie-team/yorkie/pkg/document/proxy"
 )
 
 func TestHistory(t *testing.T) {
-	clients := activeClients(t, 2)
-	c1, c2 := clients[0], clients[1]
+	clients := activeClients(t, 1)
+	cli := clients[0]
 	defer cleanupClients(t, clients)
 
+	adminCli, err := admin.Dial(defaultServer.AdminAddr())
+	assert.NoError(t, err)
+	defer func() { assert.NoError(t, adminCli.Close()) }()
+
 	t.Run("history test", func(t *testing.T) {
 		ctx := context.Background()
 
 		d1 := document.New(key.Key(t.Name()))
-		assert.NoError(t, c1.Attach(ctx, d1))
-		defer func() { assert.NoError(t, c1.Detach(ctx, d1)) }()
+		assert.NoError(t, cli.Attach(ctx, d1))
+		defer func() { assert.NoError(t, cli.Detach(ctx, d1)) }()
 
 		assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error {
 			root.SetNewArray("todos")
@@ -58,10 +63,9 @@ func TestHistory(t *testing.T) {
 			return nil
 		}, "buy bread"))
 		assert.Equal(t, `{"todos":["buy coffee","buy bread"]}`, d1.Marshal())
+		assert.NoError(t, cli.Sync(ctx))
 
-		assert.NoError(t, c1.Sync(ctx))
-
-		changes, err := c2.ListChangeSummaries(ctx, d1.Key())
+		changes, err := adminCli.ListChangeSummaries(ctx, "default", d1.Key())
 		assert.NoError(t, err)
 		assert.Len(t, changes, 3)