Repository: yorkie-team/yorkie
Pull request #522 (2023-04-25 02:55:56)

Remove panic method in crdt text
Remove panic method in crdt text
- go sdk crdt text has the condition that cause panic
- remove panic prevent shutdown server when crdt text error occurred

<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

**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 #501 

**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/522?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#522](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (459a087) into [main](https://codecov.io/gh/yorkie-team/yorkie/commit/969e50939710629c54da13d2581111512b80530f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (969e509) will **decrease** coverage by `0.09%`.
> The diff coverage is `13.63%`.

```diff
@@            Coverage Diff             @@
##             main     #522      +/-   ##
==========================================
- Coverage   50.61%   50.52%   -0.09%     
==========================================
  Files          64       64              
  Lines        5751     5763      +12     
==========================================
+ Hits         2911     2912       +1     
- Misses       2471     2481      +10     
- Partials      369      370       +1     
```


| [Impacted Files](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [pkg/document/crdt/array.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvYXJyYXkuZ28=) | `34.04% <0.00%> (-2.33%)` | :arrow_down: |
| [pkg/document/crdt/counter.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvY291bnRlci5nbw==) | `53.08% <0.00%> (ø)` | |
| [pkg/document/crdt/object.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvb2JqZWN0Lmdv) | `21.62% <0.00%> (-1.91%)` | :arrow_down: |
| [pkg/document/crdt/root.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvcm9vdC5nbw==) | `75.86% <0.00%> (-4.14%)` | :arrow_down: |
| [pkg/document/crdt/text.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvdGV4dC5nbw==) | `60.20% <0.00%> (ø)` | |
| [pkg/document/document.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2RvY3VtZW50Lmdv) | `51.35% <50.00%> (-0.77%)` | :arrow_down: |
| [pkg/document/crdt/primitive.go](https://codecov.io/gh/yorkie-team/yorkie/pull/522?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvcHJpbWl0aXZlLmdv) | `92.90% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team)

hackerwins: @emplam27 Thanks for your contribution.

`crdt.Text` uses RGATreeSplit and SplayTree. So To remove all panic from `crdt.Text`, we also need to remove panic in `RGATreeSplit` and `SplayTree`.

```
### pkg/document/crdt/rga_tree_split.go

46:// argument is nil, it would panic at runtime.
49:		panic("RGATreeSplitNodeID cannot be null")
351:		panic("the node of the given id should be found: " + s.StructureAsString())
367:		panic("offset should be less than or equal to length: " + s.StructureAsString())

### pkg/splay/splay.go
206:		panic(fmt.Sprintf(
```

https://github.com/yorkie-team/yorkie/blob/main/design/data-structure.md#overview

But since SplayTree is also used in Array, I think it would be better to remove it from another PR(e.g. Remove panic in `crdt.Array`).
emplam27: @hackerwins 
i'm done to remove `rga_tree_split`'s panic.
then, how about `element_rht` and `rga_tree_list`'s panic? is it ok to remove those panics?

```
### pkg/document/crdt/element_rht.go
173:		panic("fail to find: " + elem.CreatedAt().Key())

### pkg/document/crdt/rga_tree_list.go
209:		panic("fail to find the given createdAt: " + createdAt.Key())
241:		panic("fail to find the given prevCreatedAt: " + prevCreatedAt.Key())
246:		panic("fail to find the given createdAt: " + createdAt.Key())
261:		panic("fail to find the given prevCreatedAt: " + createdAt.Key())
278:		panic("fail to find the given createdAt: " + elem.CreatedAt().Key())
290:		panic("fail to find the given createdAt: " + createdAt.Key())
```

Patch:
diff --git a/api/converter/from_bytes.go b/api/converter/from_bytes.go
index 3b60be6a..872825ca 100644
--- a/api/converter/from_bytes.go
+++ b/api/converter/from_bytes.go
@@ -175,7 +175,6 @@ func fromJSONText(
 	}
 
 	rgaTreeSplit := crdt.NewRGATreeSplit(crdt.InitialTextNode())
-
 	current := rgaTreeSplit.InitialHead()
 	for _, pbNode := range pbText.Nodes {
 		textNode, err := fromTextNode(pbNode)
@@ -190,7 +189,7 @@ func fromJSONText(
 		if insPrevID != nil {
 			insPrevNode := rgaTreeSplit.FindNode(insPrevID)
 			if insPrevNode == nil {
-				panic("insPrevNode should be presence")
+				return nil, fmt.Errorf("insPrevNode should be presence")
 			}
 			current.SetInsPrev(insPrevNode)
 		}
diff --git a/build/charts/yorkie-argocd/README.md b/build/charts/yorkie-argocd/README.md
index 4e1e0a1b..771e19f0 100644
--- a/build/charts/yorkie-argocd/README.md
+++ b/build/charts/yorkie-argocd/README.md
@@ -10,7 +10,7 @@ Installs the yorkie-argocd, which provides GitOps based continuous delivery for
 ## Get Helm Repository Info
 
 ```bash
-helm repo add yorkie https://yorkie-team.github.io/yorkie/helm-charts
+helm repo add yorkie-team https://yorkie-team.github.io/yorkie/helm-charts
 helm repo update
 ```
 
@@ -38,31 +38,6 @@ _See [configuration](#configuration) below for custom installation_
 
 _See [`helm install`](https://helm.sh/docs/helm/helm_install/) for command documentation._
 
-## Expose Yorkie ArgoCD
-
-By default, ArgoCD Web UI is exposed via ingress with nginx ingress controller and domain `api.yorkie.dev`.
-For other environments like AWS, follow the steps below:
-
-### Expose Yorkie ArgoCD using AWS ALB
-
-If you are using AWS EKS and want to expose ArgoCD Web UI using AWS ALB, follow the steps below:
-
-```bash
-# Change externalGateway.alb.enabled to true, and certArn to your AWS certificate ARN issued in AWS Certificate Manager
-helm upgrade [RELEASE_NAME] yorkie-team/yorkie-argocd -n argocd \
-  --set externalGateway.ingressClassName=alb \
-  --set externalGateway.apiHost={YOUR_API_DOMAIN_NAME} \
-  --set externalGateway.alb.enabled=true \
-  --set externalGateway.alb.certArn={YOUR_CERTIFICATE_ARN}
-
-# Open ArgoCD Web UI
-curl https://{YOUR_API_DOMAIN_NAME}/argocd
-```
-
-Or, set configuration values in `values.yaml` file before installing the chart.
-
-_See [configuration](#configuration) below._
-
 ## Uninstall Helm Chart
 
 ```bash
diff --git a/build/charts/yorkie-argocd/templates/applications.yaml b/build/charts/yorkie-argocd/templates/applications.yaml
index 4d2dd037..3ff954ef 100644
--- a/build/charts/yorkie-argocd/templates/applications.yaml
+++ b/build/charts/yorkie-argocd/templates/applications.yaml
@@ -2,7 +2,7 @@ apiVersion: argoproj.io/v1alpha1
 kind: Application
 metadata:
   name: yorkie-cluster
-  namespace: argocd
+  namespace: {{ .Values.argocd.namespace }}
 spec:
   project: {{ .Values.argocd.project }}
   sources:
@@ -23,7 +23,7 @@ apiVersion: argoproj.io/v1alpha1
 kind: Application
 metadata:
   name: yorkie-monitoring
-  namespace: argocd
+  namespace: {{ .Values.argocd.namespace }}
 spec:
   project: {{ .Values.argocd.project }}
   sources:
diff --git a/build/charts/yorkie-argocd/templates/ingress.yaml b/build/charts/yorkie-argocd/templates/ingress.yaml
index 483e92a6..65c1beb2 100644
--- a/build/charts/yorkie-argocd/templates/ingress.yaml
+++ b/build/charts/yorkie-argocd/templates/ingress.yaml
@@ -2,23 +2,25 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: {{ .Values.argocd.name }}
-  namespace: argocd
-  {{ if .Values.externalGateway.alb.enabled }}
+  namespace: {{ .Values.argocd.namespace }}
+  {{ if .Values.ingress.alb.enabled }}
   annotations:
     alb.ingress.kubernetes.io/scheme: internet-facing
     # Set alb.ingress.kubernetes.io/certificate-arn annotation to TLS certificate's ARN issued in AWS ACM 
-    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.externalGateway.alb.certArn }}
+    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.alb.certArn }}
     alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
-    alb.ingress.kubernetes.io/group.name: {{ .Values.externalGateway.apiHost }}
+    alb.ingress.kubernetes.io/group.name: {{ .Values.ingress.hosts.apiHost }}
     alb.ingress.kubernetes.io/group.order: '2'
     {{ end }}
 spec:
-  ingressClassName: {{ .Values.externalGateway.ingressClassName }}
+  ingressClassName: {{ .Values.ingress.ingressClassName }}
   rules:
-    - host: {{ .Values.externalGateway.apiHost }}
-      http:
+    - http:
+      {{ if .Values.ingress.hosts.enabled }}
+      host: {{ .Values.ingress.hosts.apiHost }}
+      {{ end }}
         paths:
-          - path: {{ .Values.externalGateway.argoCDPath }}/
+          - path: {{ .Values.ingress.hosts.argoCDPath }}/
             pathType: Prefix
             backend:
               service:
diff --git a/build/charts/yorkie-argocd/templates/install.yaml b/build/charts/yorkie-argocd/templates/install.yaml
index da8dbe45..8073823a 100644
--- a/build/charts/yorkie-argocd/templates/install.yaml
+++ b/build/charts/yorkie-argocd/templates/install.yaml
@@ -444,7 +444,7 @@ roleRef:
 subjects:
 - kind: ServiceAccount
   name: argocd-application-controller
-  namespace: argocd
+  namespace: {{ .Values.argocd.namespace }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
@@ -461,7 +461,7 @@ roleRef:
 subjects:
 - kind: ServiceAccount
   name: argocd-server
-  namespace: argocd
+  namespace: {{ .Values.argocd.namespace }}
 ---
 apiVersion: v1
 kind: ConfigMap
@@ -676,11 +676,7 @@ metadata:
     app.kubernetes.io/part-of: argocd
   name: argocd-server
 spec:
-  {{ if .Values.externalGateway.alb.enabled }}
   type: NodePort
-  {{ else }}
-  type: LoadBalancer
-  {{ end }}
   ports:
   - name: http
     port: 80
@@ -1360,9 +1356,9 @@ spec:
         - /usr/local/bin/argocd-server
         - --insecure
         - --basehref
-        - {{ .Values.externalGateway.argoCDPath }}
+        - {{ .Values.ingress.hosts.argoCDPath }}
         - --rootpath
-        - {{ .Values.externalGateway.argoCDPath }}
+        - {{ .Values.ingress.hosts.argoCDPath }}
         env:
         - name: ARGOCD_SERVER_INSECURE
           valueFrom:
diff --git a/build/charts/yorkie-argocd/templates/repositories.yaml b/build/charts/yorkie-argocd/templates/repositories.yaml
index 4e8f674a..0bc4343f 100644
--- a/build/charts/yorkie-argocd/templates/repositories.yaml
+++ b/build/charts/yorkie-argocd/templates/repositories.yaml
@@ -3,7 +3,7 @@ apiVersion: v1
 kind: Secret
 metadata:
   name: gitops-repo
-  namespace: argocd
+  namespace: {{ .Values.argocd.namespace }}
   labels:
     argocd.argoproj.io/secret-type: repository
 stringData:
diff --git a/build/charts/yorkie-argocd/values.yaml b/build/charts/yorkie-argocd/values.yaml
index fadedf3d..f41fa62d 100644
--- a/build/charts/yorkie-argocd/values.yaml
+++ b/build/charts/yorkie-argocd/values.yaml
@@ -3,6 +3,7 @@ argocd:
   enabled: true
 
   name: yorkie-argocd
+  namespace: argocd
 
   project: default
 
@@ -13,7 +14,7 @@ argocd:
 
     # Set to GitOps repostiroy to reference helm chart custom values 
     gitOpsRepoURL: https://github.com/yorkie-team/devops.git
-    gitOpsRepoTargetRevision: manifest-update
+    gitOpsRepoTargetRevision: main
     
     # Set to path of each helm chart custom values from GitOps repository
     clusterValuesPath: k8s/cluster/values.yaml
@@ -29,15 +30,17 @@ argocd:
     yorkieNamespace: istio-system
     monitoringNamespace: monitoring
 
-# Configuration for external gateway (eg: AWS ALB)
-externalGateway:
+# Configuration for ingress (eg: AWS ALB)
+ingress:
   ingressClassName: nginx
   ## Set to alb if you are using AWS ALB
   # ingressClassName: alb
 
   # Use same host for yorkie cluster
-  apiHost: api.yorkie.dev
-  argoCDPath: /argocd
+  hosts:
+    enabled: false
+    apiHost: api.yorkie.dev
+    argoCDPath: /argocd
 
   alb:
     enabled: false
diff --git a/build/charts/yorkie-cluster/README.md b/build/charts/yorkie-cluster/README.md
index 41e92d88..4eca1137 100644
--- a/build/charts/yorkie-cluster/README.md
+++ b/build/charts/yorkie-cluster/README.md
@@ -19,7 +19,7 @@ istioctl install -f <(curl -s https://raw.githubusercontent.com/yorkie-team/york
 ## Get Helm Repository Info
 
 ```bash
-helm repo add yorkie https://yorkie-team.github.io/yorkie/helm-charts
+helm repo add yorkie-team https://yorkie-team.github.io/yorkie/helm-charts
 helm repo update
 ```
 
@@ -29,43 +29,17 @@ _See [`helm repo`](https://helm.sh/docs/helm/helm_repo/) for command documentati
 
 ```bash
 # Install yorkie cluster helm chart
-helm install [RELEASE_NAME] yorkie-team/yorkie-cluster -n istio-system --create-namespace
+helm install [RELEASE_NAME] yorkie-team/yorkie-cluster
 ```
 
 _See [configuration](#configuration) below for custom installation_
 
 _See [`helm install`](https://helm.sh/docs/helm/helm_install/) for command documentation._
 
-## Expose Yorkie Cluster
-
-By default, Yorkie API is exposed via ingress with nginx ingress controller and domain `api.yorkie.dev`.
-For other environments like AWS, follow the steps below:
-
-## Expose Yorkie Cluster using AWS ALB
-
-If you are using AWS EKS and want to expose Yorkie Cluster using AWS ALB, follow the steps below:
-
-```bash
-# Enable ALB, and change domain name and certificate arn for ALB
-helm upgrade [RELEASE_NAME] yorkie-team/yorkie-cluster -n istio-system \
-    --set externalGateway.ingressClassName=alb \
-    --set externalGateway.apiHost={YOUR_API_DOMAIN_NAME} \
-    --set externalGateway.adminHost={YOUR_ADMIN_DOMAIN_NAME} \
-    --set externalGateway.alb.enabled=true \
-    --set externalGateway.alb.certArn={YOUR_CERTIFICATE_ARN}
-
-# Test Yorkie API
-const client = new yorkie.Client('{YOUR_API_DOMAIN_NAME}');
-```
-
-Or, set configuration values in `values.yaml` file before installing the chart.
-
-_See [configuration](#configuration) below._
-
 ## Uninstall Helm Chart
 
 ```bash
-helm uninstall [RELEASE_NAME] -n istio-system
+helm uninstall [RELEASE_NAME]
 ```
 
 This removes all the Kubernetes components associated with the chart and deletes the release.
@@ -83,7 +57,7 @@ This will remove all the istio components including CRDs.
 ## Upgrading Chart
 
 ```bash
-helm upgrade [RELEASE_NAME] yorkie-team/yorkie-cluster -n istio-system
+helm upgrade [RELEASE_NAME] yorkie-team/yorkie-cluster
 ```
 
 With Helm v3, CRDs created by this chart are not updated by default and should be manually updated.
diff --git a/build/charts/yorkie-cluster/templates/NOTES.txt b/build/charts/yorkie-cluster/templates/NOTES.txt
index d5cd8a09..3da9146c 100644
--- a/build/charts/yorkie-cluster/templates/NOTES.txt
+++ b/build/charts/yorkie-cluster/templates/NOTES.txt
@@ -5,5 +5,5 @@ For next steps, follow:
   $ curl https://github.com/yorkie-team/yorkie/tree/main/charts/yorkie-cluster
   
 To learn more about the release, try:
-  $ helm status {{ .Release.Name }} -n istio-system
-  $ helm get all {{ .Release.Name }} -n istio-system
+  $ helm status {{ .Release.Name }}
+  $ helm get all {{ .Release.Name }}
diff --git a/build/charts/yorkie-cluster/templates/ingress.yaml b/build/charts/yorkie-cluster/templates/ingress.yaml
index ca9e717d..c7c6af7d 100644
--- a/build/charts/yorkie-cluster/templates/ingress.yaml
+++ b/build/charts/yorkie-cluster/templates/ingress.yaml
@@ -3,22 +3,24 @@ kind: Ingress
 metadata:
   name: {{ .Values.yorkie.name }}
   namespace: istio-system
-  {{ if .Values.externalGateway.alb.enabled }}
+  {{ if .Values.ingress.alb.enabled }}
   annotations:
     alb.ingress.kubernetes.io/scheme: internet-facing
     # Set alb.ingress.kubernetes.io/certificate-arn annotation to TLS certificate's ARN issued in AWS ACM 
-    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.externalGateway.alb.certArn }}
+    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.alb.certArn }}
     alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]'
     alb.ingress.kubernetes.io/backend-protocol: HTTP
     alb.ingress.kubernetes.io/backend-protocol-version: HTTP2
-    alb.ingress.kubernetes.io/group.name: {{ .Values.externalGateway.apiHost }}
+    alb.ingress.kubernetes.io/group.name: {{ .Values.ingress.hosts.apiHost }}
     alb.ingress.kubernetes.io/group.order: '10'
   {{ end }}
 spec:
-  ingressClassName: {{ .Values.externalGateway.ingressClassName }}
+  ingressClassName: {{ .Values.ingress.ingressClassName }}
   rules:
-    - host: {{ .Values.externalGateway.apiHost }}
-      http:
+    - http:
+      {{ if .Values.ingress.hosts.enabled }}
+      host: {{ .Values.ingress.hosts.apiHost }}
+      {{ end }}
         paths:
           - path: /
             pathType: Prefix
@@ -33,21 +35,23 @@ kind: Ingress
 metadata:
   name: {{ .Values.yorkie.name }}-admin
   namespace: istio-system
-  {{ if .Values.externalGateway.alb.enabled }}
+  {{ if .Values.ingress.alb.enabled }}
   annotations:
     alb.ingress.kubernetes.io/scheme: internet-facing
     # Set alb.ingress.kubernetes.io/certificate-arn annotation to TLS certificate's ARN issued in AWS ACM 
-    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.externalGateway.alb.certArn }}
+    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.alb.certArn }}
     alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
     alb.ingress.kubernetes.io/backend-protocol: HTTP
-    alb.ingress.kubernetes.io/group.name: {{ .Values.externalGateway.apiHost }}
+    alb.ingress.kubernetes.io/group.name: {{ .Values.ingress.hosts.apiHost }}
     alb.ingress.kubernetes.io/group.order: '3'
   {{ end }}
 spec:
-  ingressClassName: {{ .Values.externalGateway.ingressClassName }}
+  ingressClassName: {{ .Values.ingress.ingressClassName }}
   rules:
-    - host: {{ .Values.externalGateway.adminHost }}
-      http:
+    - http:
+      {{ if .Values.ingress.hosts.enabled }}
+      host: {{ .Values.ingress.hosts.adminHost }}
+      {{ end }}
         paths:
           - path: /
             pathType: Prefix
diff --git a/build/charts/yorkie-cluster/values.yaml b/build/charts/yorkie-cluster/values.yaml
index c3d57da5..58ad2775 100644
--- a/build/charts/yorkie-cluster/values.yaml
+++ b/build/charts/yorkie-cluster/values.yaml
@@ -55,14 +55,16 @@ ingressGateway:
 
   resources: {}
 
-# Configuration for external gateway (eg: AWS ALB)
-externalGateway:
+# Configuration for ingress (eg: AWS ALB)
+ingress:
   ingressClassName: nginx
   ## Set to alb if you are using AWS ALB
   # ingressClassName: alb
 
-  apiHost: api.yorkie.dev
-  adminHost: admin.yorkie.dev
+  hosts:
+    enabled: false
+    apiHost: api.yorkie.dev
+    adminHost: admin.yorkie.dev
     
   alb:
     enabled: false
diff --git a/build/charts/yorkie-monitoring/README.md b/build/charts/yorkie-monitoring/README.md
index c68e7b8a..2f819bf0 100644
--- a/build/charts/yorkie-monitoring/README.md
+++ b/build/charts/yorkie-monitoring/README.md
@@ -10,7 +10,7 @@ Installs the yorkie-monitoring, which provides monitoring system with prometheus
 ## Get Helm Repository Info
 
 ```bash
-helm repo add yorkie https://yorkie-team.github.io/yorkie/helm-charts
+helm repo add yorkie-team https://yorkie-team.github.io/yorkie/helm-charts
 helm repo update
 ```
 
@@ -21,45 +21,12 @@ _See [`helm repo`](https://helm.sh/docs/helm/helm_repo/) for command documentati
 ```bash
 # Install yorkie monitoring helm chart
 helm install [RELEASE_NAME] yorkie-team/yorkie-monitoring -n monitoring --create-namespace
-
-# Configure Loki datasource in Grafana
-# Set Loki datasource url to http://yorkie-monitoring-loki:3100
-curl https://{YOUR_API_DOMAIN_NAME}/grafana/datasources
-
-# Import yorkie grafana dashboard and Loki grafana dashboard
-curl https://grafana.com/grafana/dashboards/18451-yorkie-dashboard/
-curl https://grafana.com/grafana/dashboards/13186-loki-dashboard/
 ```
 
 _See [configuration](#configuration) below for custom installation_
 
 _See [`helm install`](https://helm.sh/docs/helm/helm_install/) for command documentation._
 
-## Expose Yorkie Monitoring
-
-By default, Grafana web dashboard is exposed via ingress with nginx ingress controller and domain `api.yorkie.dev`.
-For other environments like AWS, follow the steps below:
-
-## Expose Yorkie Monitoring using AWS ALB
-
-If you are using AWS EKS and want to expose Grafana Dashboard using AWS ALB, follow the steps below:
-
-```bash
-# Change externalGateway.alb.enabled to true, and certArn to your AWS certificate ARN issued in AWS Certificate Manager
-helm upgrade [RELEASE_NAME] yorkie-team/yorkie-monitoring -n monitoring \
-  --set externalGateway.ingressClassName=alb \
-  --set externalGateway.apiHost={YOUR_API_DOMAIN_NAME} \
-  --set externalGateway.alb.enabled=true \
-  --set externalGateway.alb.certArn={YOUR_CERTIFICATE_ARN}
-
-# Open Grafana Dashboard
-curl https://{YOUR_API_DOMAIN_NAME}/grafana
-```
-
-Or, set configuration values in `values.yaml` file before installing the chart.
-
-_See [configuration](#configuration) below._
-
 ## Dependencies
 
 By default this chart installs additional, dependent charts:
diff --git a/build/charts/yorkie-monitoring/templates/ingress.yaml b/build/charts/yorkie-monitoring/templates/ingress.yaml
index 8fe1583d..e7e30b23 100644
--- a/build/charts/yorkie-monitoring/templates/ingress.yaml
+++ b/build/charts/yorkie-monitoring/templates/ingress.yaml
@@ -2,24 +2,26 @@ apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: {{ .Values.monitoring.name }}
-  namespace: monitoring
-  {{ if .Values.externalGateway.alb.enabled }}
+  namespace: {{ .Values.monitoring.namespace }}
+  {{ if .Values.ingress.alb.enabled }}
   annotations:
     alb.ingress.kubernetes.io/scheme: internet-facing
     # Set alb.ingress.kubernetes.io/certificate-arn annotation to TLS certificate's ARN issued in AWS ACM
-    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.externalGateway.alb.certArn }}
+    alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.alb.certArn }}
     alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
     alb.ingress.kubernetes.io/backend-protocol: HTTP
-    alb.ingress.kubernetes.io/group.name: {{ .Values.externalGateway.apiHost }}
+    alb.ingress.kubernetes.io/group.name: {{ .Values.ingress.hosts.apiHost }}
     alb.ingress.kubernetes.io/group.order: '1'
     {{ end }}
 spec:
-  ingressClassName: {{ .Values.externalGateway.ingressClassName }}
+  ingressClassName: {{ .Values.ingress.ingressClassName }}
   rules:
-    - host: {{ .Values.externalGateway.apiHost }}
-      http:
+    - http:
+      {{ if .Values.ingress.hosts.enabled }}
+      host: {{ .Values.ingress.hosts.apiHost }}
+      {{ end }}
         paths:
-          - path: {{ .Values.externalGateway.grafanaPath }}/
+          - path: {{ .Values.ingress.hosts.grafanaPath }}/
             pathType: Prefix
             backend:
               service:
diff --git a/build/charts/yorkie-monitoring/templates/yorkie-servicemonitor.yaml b/build/charts/yorkie-monitoring/templates/yorkie-servicemonitor.yaml
index 1d20a9fb..7ba50c38 100644
--- a/build/charts/yorkie-monitoring/templates/yorkie-servicemonitor.yaml
+++ b/build/charts/yorkie-monitoring/templates/yorkie-servicemonitor.yaml
@@ -11,7 +11,7 @@ metadata:
     app.kubernetes.io/managed-by: Helm
     prometheus: monitoring
   name: {{ .Values.monitoring.name }}
-  namespace: monitoring
+  namespace: {{ .Values.monitoring.namespace }}
 spec:
   endpoints:
   - path: /metrics
diff --git a/build/charts/yorkie-monitoring/values.yaml b/build/charts/yorkie-monitoring/values.yaml
index 88564414..5dc7db20 100644
--- a/build/charts/yorkie-monitoring/values.yaml
+++ b/build/charts/yorkie-monitoring/values.yaml
@@ -1,6 +1,7 @@
 # Configuration for monitoring system
 monitoring:
   name: &monitoringName yorkie-monitoring
+  namespace: &monitoringNamespace monitoring
 
   # Name of yorkie used for yorkie cluster
   yorkieName: yorkie
@@ -8,15 +9,17 @@ monitoring:
 
   enableEnhancements: &monitoringEnhancementsEnable false
 
-# Configuration for external gateway (eg: AWS ALB)
-externalGateway:
+# Configuration for ingress (eg: AWS ALB)
+ingress:
   ingressClassName: nginx
   ## Set to alb if you are using AWS ALB
   # ingressClassName: alb
 
   # Use same host for yorkie cluster
-  apiHost: &apiHost api.yorkie.dev
-  grafanaPath: /grafana
+  hosts:
+    enabled: false
+    apiHost: &apiHost api.yorkie.dev
+    grafanaPath: /grafana
 
   alb:
     enabled: false
@@ -26,7 +29,7 @@ externalGateway:
 # Configuration for manual prometheus monitoring stack
 kube-prometheus-stack:
   fullnameOverride: *monitoringName
-  namespaceOverride: monitoring
+  namespaceOverride: *monitoringNamespace
 
   # Configuration for alertmanager
   # ref: https://prometheus.io/docs/alerting/alertmanager/
@@ -64,6 +67,38 @@ kube-prometheus-stack:
       
       service:
         type: NodePort
+    
+    # For grafana dashboards
+    dashboardProviders:
+      dashboardproviders.yaml:
+        apiVersion: 1
+        providers:
+        - name: 'yorkie'
+          orgId: 1
+          folder: ''
+          type: file
+          disableDeletion: false
+          editable: true
+          options:
+            path: /var/lib/grafana/dashboards/yorkie
+
+    # For grafana dashboards
+    dashboards:
+      yorkie:
+        yorkie-dashboard:
+          gnetId: 18560
+          revision: 2
+          datasource:
+          - name: DS_PROMETHEUS
+            value: Prometheus
+        loki-dashboard:
+          gnetId: 13186
+          revision: 1
+          datasource:
+          - name: DS_PROMETHEUS
+            value: Prometheus
+          - name: DS_LOKI
+            value: Loki
 
   # Component scraping the kube api server
   kubeApiServer:
@@ -139,7 +174,7 @@ kube-prometheus-stack:
 # Configuration for loki monitoring stack
 loki-stack:
   loki:
-    # Use 
+    isDefault: false
     fullnameOverride: yorkie-monitoring-loki
 
     persistence:
diff --git a/pkg/document/crdt/array.go b/pkg/document/crdt/array.go
index df5be3e4..e6f5cb91 100644
--- a/pkg/document/crdt/array.go
+++ b/pkg/document/crdt/array.go
@@ -95,16 +95,20 @@ func (a *Array) StructureAsString() string {
 }
 
 // DeepCopy copies itself deeply.
-func (a *Array) DeepCopy() Element {
+func (a *Array) DeepCopy() (Element, error) {
 	elements := NewRGATreeList()
 
 	for _, node := range a.elements.Nodes() {
-		elements.Add(node.elem.DeepCopy())
+		copiedNode, err := node.elem.DeepCopy()
+		if err != nil {
+			return nil, err
+		}
+		elements.Add(copiedNode)
 	}
 
 	array := NewArray(elements, a.createdAt)
 	array.removedAt = a.removedAt
-	return array
+	return array, nil
 }
 
 // CreatedAt returns the creation time of this array.
diff --git a/pkg/document/crdt/counter.go b/pkg/document/crdt/counter.go
index b31d57df..0bf7c515 100644
--- a/pkg/document/crdt/counter.go
+++ b/pkg/document/crdt/counter.go
@@ -96,9 +96,9 @@ func (p *Counter) Marshal() string {
 }
 
 // DeepCopy copies itself deeply.
-func (p *Counter) DeepCopy() Element {
+func (p *Counter) DeepCopy() (Element, error) {
 	counter := *p
-	return &counter
+	return &counter, nil
 }
 
 // CreatedAt returns the creation time.
diff --git a/pkg/document/crdt/element.go b/pkg/document/crdt/element.go
index c915b8c8..c2f155b1 100644
--- a/pkg/document/crdt/element.go
+++ b/pkg/document/crdt/element.go
@@ -47,7 +47,7 @@ type Element interface {
 	Marshal() string
 
 	// DeepCopy copies itself deeply.
-	DeepCopy() Element
+	DeepCopy() (Element, error)
 
 	// CreatedAt returns the creation time of this element.
 	CreatedAt() *time.Ticket
diff --git a/pkg/document/crdt/object.go b/pkg/document/crdt/object.go
index f93fc84b..2793261d 100644
--- a/pkg/document/crdt/object.go
+++ b/pkg/document/crdt/object.go
@@ -94,16 +94,20 @@ func (o *Object) Marshal() string {
 }
 
 // DeepCopy copies itself deeply.
-func (o *Object) DeepCopy() Element {
+func (o *Object) DeepCopy() (Element, error) {
 	members := NewElementRHT()
 
 	for _, node := range o.memberNodes.Nodes() {
-		members.Set(node.key, node.elem.DeepCopy())
+		copiedNode, err := node.elem.DeepCopy()
+		if err != nil {
+			return nil, err
+		}
+		members.Set(node.key, copiedNode)
 	}
 
 	obj := NewObject(members, o.createdAt)
 	obj.removedAt = o.removedAt
-	return obj
+	return obj, nil
 }
 
 // CreatedAt returns the creation time of this object.
diff --git a/pkg/document/crdt/primitive.go b/pkg/document/crdt/primitive.go
index 2bdc94b3..0308db5c 100644
--- a/pkg/document/crdt/primitive.go
+++ b/pkg/document/crdt/primitive.go
@@ -219,9 +219,9 @@ func (p *Primitive) Marshal() string {
 }
 
 // DeepCopy copies itself deeply.
-func (p *Primitive) DeepCopy() Element {
+func (p *Primitive) DeepCopy() (Element, error) {
 	primitive := *p
-	return &primitive
+	return &primitive, nil
 }
 
 // CreatedAt returns the creation time.
diff --git a/pkg/document/crdt/rga_tree_split.go b/pkg/document/crdt/rga_tree_split.go
index 12a9aca3..58214263 100644
--- a/pkg/document/crdt/rga_tree_split.go
+++ b/pkg/document/crdt/rga_tree_split.go
@@ -43,7 +43,8 @@ func NewRGATreeSplitNodeID(createdAt *time.Ticket, offset int) *RGATreeSplitNode
 
 // Compare returns an integer comparing two ID. The result will be 0 if
 // id==other, -1 if id < other, and +1 if id > other. If the receiver or
-// argument is nil, it would panic at runtime.
+// argument is nil, it would panic at runtime. This method is following
+// golang standard interface.
 func (id *RGATreeSplitNodeID) Compare(other llrb.Key) int {
 	if id == nil || other == nil {
 		panic("RGATreeSplitNodeID cannot be null")
@@ -330,47 +331,53 @@ func (s *RGATreeSplit[V]) findNodePos(index int) *RGATreeSplitNodePos {
 func (s *RGATreeSplit[V]) findNodeWithSplit(
 	pos *RGATreeSplitNodePos,
 	updatedAt *time.Ticket,
-) (*RGATreeSplitNode[V], *RGATreeSplitNode[V]) {
+) (*RGATreeSplitNode[V], *RGATreeSplitNode[V], error) {
 	absoluteID := pos.getAbsoluteID()
-	node := s.findFloorNodePreferToLeft(absoluteID)
+	node, err := s.findFloorNodePreferToLeft(absoluteID)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	relativeOffset := absoluteID.offset - node.id.offset
 
-	s.splitNode(node, relativeOffset)
+	_, err = s.splitNode(node, relativeOffset)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	for node.next != nil && node.next.createdAt().After(updatedAt) {
 		node = node.next
 	}
 
-	return node, node.next
+	return node, node.next, nil
 }
 
-func (s *RGATreeSplit[V]) findFloorNodePreferToLeft(id *RGATreeSplitNodeID) *RGATreeSplitNode[V] {
+func (s *RGATreeSplit[V]) findFloorNodePreferToLeft(id *RGATreeSplitNodeID) (*RGATreeSplitNode[V], error) {
 	node := s.findFloorNode(id)
 	if node == nil {
-		panic("the node of the given id should be found: " + s.StructureAsString())
+		return nil, fmt.Errorf("the node of the given id should be found: " + s.StructureAsString())
 	}
 
 	if id.offset > 0 && node.id.offset == id.offset {
 		// NOTE: InsPrev may not be present due to GC.
 		if node.insPrev == nil {
-			return node
+			return node, nil
 		}
 		node = node.insPrev
 	}
 
-	return node
+	return node, nil
 }
 
-func (s *RGATreeSplit[V]) splitNode(node *RGATreeSplitNode[V], offset int) *RGATreeSplitNode[V] {
+func (s *RGATreeSplit[V]) splitNode(node *RGATreeSplitNode[V], offset int) (*RGATreeSplitNode[V], error) {
 	if offset > node.contentLen() {
-		panic("offset should be less than or equal to length: " + s.StructureAsString())
+		return nil, fmt.Errorf("offset should be less than or equal to length: " + s.StructureAsString())
 	}
 
 	if offset == 0 {
-		return node
+		return node, nil
 	} else if offset == node.contentLen() {
-		return node.next
+		return node.next, nil
 	}
 
 	splitNode := node.split(offset)
@@ -383,7 +390,7 @@ func (s *RGATreeSplit[V]) splitNode(node *RGATreeSplitNode[V], offset int) *RGAT
 	}
 	splitNode.SetInsPrev(node)
 
-	return splitNode
+	return splitNode, nil
 }
 
 // InsertAfter inserts the given node after the given previous node.
@@ -439,10 +446,16 @@ func (s *RGATreeSplit[V]) edit(
 	latestCreatedAtMapByActor map[string]*time.Ticket,
 	content V,
 	editedAt *time.Ticket,
-) (*RGATreeSplitNodePos, map[string]*time.Ticket) {
+) (*RGATreeSplitNodePos, map[string]*time.Ticket, error) {
 	// 01. Split nodes with from and to
-	toLeft, toRight := s.findNodeWithSplit(to, editedAt)
-	fromLeft, fromRight := s.findNodeWithSplit(from, editedAt)
+	toLeft, toRight, err := s.findNodeWithSplit(to, editedAt)
+	if err != nil {
+		return nil, nil, err
+	}
+	fromLeft, fromRight, err := s.findNodeWithSplit(from, editedAt)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	// 02. delete between from and to
 	nodesToDelete := s.findBetween(fromRight, toRight)
@@ -467,7 +480,7 @@ func (s *RGATreeSplit[V]) edit(
 		s.removedNodeMap[key] = removedNode
 	}
 
-	return caretPos, latestCreatedAtMap
+	return caretPos, latestCreatedAtMap, nil
 }
 
 func (s *RGATreeSplit[V]) findBetween(from, to *RGATreeSplitNode[V]) []*RGATreeSplitNode[V] {
diff --git a/pkg/document/crdt/root.go b/pkg/document/crdt/root.go
index 9d445434..7aa7a2dd 100644
--- a/pkg/document/crdt/root.go
+++ b/pkg/document/crdt/root.go
@@ -101,8 +101,12 @@ func (r *Root) RegisterTextElementWithGarbage(textType TextElement) {
 }
 
 // DeepCopy copies itself deeply.
-func (r *Root) DeepCopy() *Root {
-	return NewRoot(r.object.DeepCopy().(*Object))
+func (r *Root) DeepCopy() (*Root, error) {
+	copiedObject, err := r.object.DeepCopy()
+	if err != nil {
+		return nil, err
+	}
+	return NewRoot(copiedObject.(*Object)), nil
 }
 
 // GarbageCollect purge elements that were removed before the given time.
diff --git a/pkg/document/crdt/text.go b/pkg/document/crdt/text.go
index dac3f6e3..4f235db5 100644
--- a/pkg/document/crdt/text.go
+++ b/pkg/document/crdt/text.go
@@ -166,23 +166,23 @@ func (t *Text) Marshal() string {
 }
 
 // DeepCopy copies itself deeply.
-func (t *Text) DeepCopy() Element {
+func (t *Text) DeepCopy() (Element, error) {
 	rgaTreeSplit := NewRGATreeSplit(InitialTextNode())
-
 	current := rgaTreeSplit.InitialHead()
+
 	for _, node := range t.Nodes() {
 		current = rgaTreeSplit.InsertAfter(current, node.DeepCopy())
 		insPrevID := node.InsPrevID()
 		if insPrevID != nil {
 			insPrevNode := rgaTreeSplit.FindNode(insPrevID)
 			if insPrevNode == nil {
-				panic("insPrevNode should be presence")
+				return nil, fmt.Errorf("insPrevNode should be presence")
 			}
 			current.SetInsPrev(insPrevNode)
 		}
 	}
 
-	return NewText(rgaTreeSplit, t.createdAt)
+	return NewText(rgaTreeSplit, t.createdAt), nil
 }
 
 // CreatedAt returns the creation time of this Text.
@@ -233,21 +233,19 @@ func (t *Text) Edit(
 	content string,
 	attributes map[string]string,
 	executedAt *time.Ticket,
-) (*RGATreeSplitNodePos, map[string]*time.Ticket) {
+) (*RGATreeSplitNodePos, map[string]*time.Ticket, error) {
 	val := NewTextValue(content, NewRHT())
 	for key, value := range attributes {
 		val.attrs.Set(key, value, executedAt)
 	}
 
-	cursorPos, latestCreatedAtMapByActor := t.rgaTreeSplit.edit(
+	return t.rgaTreeSplit.edit(
 		from,
 		to,
 		latestCreatedAtMapByActor,
 		val,
 		executedAt,
 	)
-
-	return cursorPos, latestCreatedAtMapByActor
 }
 
 // Style applies the given attributes of the given range.
@@ -256,10 +254,16 @@ func (t *Text) Style(
 	to *RGATreeSplitNodePos,
 	attributes map[string]string,
 	executedAt *time.Ticket,
-) {
+) error {
 	// 01. Split nodes with from and to
-	_, toRight := t.rgaTreeSplit.findNodeWithSplit(to, executedAt)
-	_, fromRight := t.rgaTreeSplit.findNodeWithSplit(from, executedAt)
+	_, toRight, err := t.rgaTreeSplit.findNodeWithSplit(to, executedAt)
+	if err != nil {
+		return err
+	}
+	_, fromRight, err := t.rgaTreeSplit.findNodeWithSplit(from, executedAt)
+	if err != nil {
+		return err
+	}
 
 	// 02. style nodes between from and to
 	nodes := t.rgaTreeSplit.findBetween(fromRight, toRight)
@@ -269,6 +273,7 @@ func (t *Text) Style(
 			val.attrs.Set(key, value, executedAt)
 		}
 	}
+	return nil
 }
 
 // Select stores that the given range has been selected.
diff --git a/pkg/document/document.go b/pkg/document/document.go
index cc1bcbaf..821a9126 100644
--- a/pkg/document/document.go
+++ b/pkg/document/document.go
@@ -59,7 +59,9 @@ func (d *Document) Update(
 		return ErrDocumentRemoved
 	}
 
-	d.ensureClone()
+	if err := d.ensureClone(); err != nil {
+		return err
+	}
 
 	ctx := change.NewContext(
 		d.doc.changeID.Next(),
@@ -95,7 +97,9 @@ func (d *Document) ApplyChangePack(pack *change.Pack) error {
 			return err
 		}
 	} else {
-		d.ensureClone()
+		if err := d.ensureClone(); err != nil {
+			return err
+		}
 
 		for _, c := range pack.Changes {
 			if err := c.Execute(d.clone); err != nil {
@@ -194,7 +198,9 @@ func (d *Document) RootObject() *crdt.Object {
 
 // Root returns the root object of this document.
 func (d *Document) Root() *json.Object {
-	d.ensureClone()
+	if err := d.ensureClone(); err != nil {
+		panic(err)
+	}
 
 	ctx := change.NewContext(d.doc.changeID.Next(), "", d.clone)
 	return json.NewObject(ctx, d.clone.Object())
@@ -213,10 +219,15 @@ func (d *Document) GarbageLen() int {
 	return d.doc.GarbageLen()
 }
 
-func (d *Document) ensureClone() {
+func (d *Document) ensureClone() error {
 	if d.clone == nil {
-		d.clone = d.doc.root.DeepCopy()
+		copiedDoc, err := d.doc.root.DeepCopy()
+		if err != nil {
+			return err
+		}
+		d.clone = copiedDoc
 	}
+	return nil
 }
 
 func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
diff --git a/pkg/document/json/array.go b/pkg/document/json/array.go
index 1c0ba8e0..ab6f164f 100644
--- a/pkg/document/json/array.go
+++ b/pkg/document/json/array.go
@@ -186,10 +186,14 @@ func (p *Array) insertAfterInternal(
 	elem := creator(ticket)
 	value := toOriginal(elem)
 
+	copiedValue, err := value.DeepCopy()
+	if err != nil {
+		panic(err)
+	}
 	p.context.Push(operations.NewAdd(
 		p.Array.CreatedAt(),
 		prevCreatedAt,
-		value.DeepCopy(),
+		copiedValue,
 		ticket,
 	))
 
diff --git a/pkg/document/json/object.go b/pkg/document/json/object.go
index c951d7fd..8255fedb 100644
--- a/pkg/document/json/object.go
+++ b/pkg/document/json/object.go
@@ -257,10 +257,14 @@ func (p *Object) setInternal(
 	elem := creator(ticket)
 	value := toOriginal(elem)
 
+	copiedValue, err := value.DeepCopy()
+	if err != nil {
+		panic(err)
+	}
 	p.context.Push(operations.NewSet(
 		p.CreatedAt(),
 		k,
-		value.DeepCopy(),
+		copiedValue,
 		ticket,
 	))
 
diff --git a/pkg/document/json/text.go b/pkg/document/json/text.go
index 4b4f656e..f667a7e2 100644
--- a/pkg/document/json/text.go
+++ b/pkg/document/json/text.go
@@ -52,7 +52,7 @@ func (p *Text) Edit(from, to int, content string, attributes ...map[string]strin
 	}
 
 	ticket := p.context.IssueTimeTicket()
-	_, maxCreationMapByActor := p.Text.Edit(
+	_, maxCreationMapByActor, err := p.Text.Edit(
 		fromPos,
 		toPos,
 		nil,
@@ -60,6 +60,9 @@ func (p *Text) Edit(from, to int, content string, attributes ...map[string]strin
 		attrs,
 		ticket,
 	)
+	if err != nil {
+		panic(err)
+	}
 
 	p.context.Push(operations.NewEdit(
 		p.CreatedAt(),
@@ -85,12 +88,14 @@ func (p *Text) Style(from, to int, attributes map[string]string) *Text {
 	fromPos, toPos := p.Text.CreateRange(from, to)
 
 	ticket := p.context.IssueTimeTicket()
-	p.Text.Style(
+	if err := p.Text.Style(
 		fromPos,
 		toPos,
 		attributes,
 		ticket,
-	)
+	); err != nil {
+		panic(err)
+	}
 
 	p.context.Push(operations.NewStyle(
 		p.CreatedAt(),
diff --git a/pkg/document/operations/add.go b/pkg/document/operations/add.go
index eb4be7aa..d2e1ec58 100644
--- a/pkg/document/operations/add.go
+++ b/pkg/document/operations/add.go
@@ -60,7 +60,10 @@ func (o *Add) Execute(root *crdt.Root) error {
 		return ErrNotApplicableDataType
 	}
 
-	value := o.value.DeepCopy()
+	value, err := o.value.DeepCopy()
+	if err != nil {
+		return err
+	}
 	obj.InsertAfter(o.prevCreatedAt, value)
 
 	root.RegisterElement(value)
diff --git a/pkg/document/operations/edit.go b/pkg/document/operations/edit.go
index 98cab3ed..b1fd50ba 100644
--- a/pkg/document/operations/edit.go
+++ b/pkg/document/operations/edit.go
@@ -75,7 +75,10 @@ func (e *Edit) Execute(root *crdt.Root) error {
 
 	switch obj := parent.(type) {
 	case *crdt.Text:
-		obj.Edit(e.from, e.to, e.latestCreatedAtMapByActor, e.content, e.attributes, e.executedAt)
+		_, _, err := obj.Edit(e.from, e.to, e.latestCreatedAtMapByActor, e.content, e.attributes, e.executedAt)
+		if err != nil {
+			return err
+		}
 		if !e.from.Equal(e.to) {
 			root.RegisterTextElementWithGarbage(obj)
 		}
diff --git a/pkg/document/operations/set.go b/pkg/document/operations/set.go
index 398bdd16..2e2a66ce 100644
--- a/pkg/document/operations/set.go
+++ b/pkg/document/operations/set.go
@@ -61,7 +61,10 @@ func (o *Set) Execute(root *crdt.Root) error {
 		return ErrNotApplicableDataType
 	}
 
-	value := o.value.DeepCopy()
+	value, err := o.value.DeepCopy()
+	if err != nil {
+		return err
+	}
 	removed := obj.Set(o.key, value)
 	root.RegisterElement(value)
 	if removed != nil {
diff --git a/pkg/document/operations/style.go b/pkg/document/operations/style.go
index 46484c0f..069f8a4d 100644
--- a/pkg/document/operations/style.go
+++ b/pkg/document/operations/style.go
@@ -64,8 +64,7 @@ func (e *Style) Execute(root *crdt.Root) error {
 		return ErrNotApplicableDataType
 	}
 
-	obj.Style(e.from, e.to, e.attributes, e.executedAt)
-	return nil
+	return obj.Style(e.from, e.to, e.attributes, e.executedAt)
 }
 
 // From returns the start point of the editing range.


Test patch:
diff --git a/pkg/document/crdt/primitive_test.go b/pkg/document/crdt/primitive_test.go
index ce5110f4..b209875a 100644
--- a/pkg/document/crdt/primitive_test.go
+++ b/pkg/document/crdt/primitive_test.go
@@ -53,7 +53,8 @@ func TestPrimitive(t *testing.T) {
 			assert.Equal(t, prim.Value(), crdt.ValueFromBytes(prim.ValueType(), prim.Bytes()))
 			assert.Equal(t, prim.Marshal(), test.marshal)
 
-			copied := prim.DeepCopy()
+			copied, err := prim.DeepCopy()
+			assert.NoError(t, err)
 			assert.Equal(t, prim.CreatedAt(), copied.CreatedAt())
 			assert.Equal(t, prim.MovedAt(), copied.MovedAt())
 			assert.Equal(t, prim.Marshal(), copied.Marshal())
diff --git a/pkg/document/crdt/root_test.go b/pkg/document/crdt/root_test.go
index f4d72f05..a78bca53 100644
--- a/pkg/document/crdt/root_test.go
+++ b/pkg/document/crdt/root_test.go
@@ -59,25 +59,29 @@ func TestRoot(t *testing.T) {
 		text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket())
 
 		fromPos, toPos := text.CreateRange(0, 0)
-		text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		_, _, err := text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, "Hello World", text.String())
 		assert.Equal(t, 0, root.GarbageLen())
 
 		fromPos, toPos = text.CreateRange(5, 10)
-		text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, "HelloYorkied", text.String())
 		assert.Equal(t, 1, root.GarbageLen())
 
 		fromPos, toPos = text.CreateRange(0, 5)
-		text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, "Yorkied", text.String())
 		assert.Equal(t, 2, root.GarbageLen())
 
 		fromPos, toPos = text.CreateRange(6, 7)
-		text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, "Yorkie", text.String())
 		assert.Equal(t, 3, root.GarbageLen())
@@ -115,7 +119,8 @@ func TestRoot(t *testing.T) {
 
 		for _, tc := range tests {
 			fromPos, toPos := text.CreateRange(tc.from, tc.to)
-			text.Edit(fromPos, toPos, nil, tc.content, nil, ctx.IssueTimeTicket())
+			_, _, err := text.Edit(fromPos, toPos, nil, tc.content, nil, ctx.IssueTimeTicket())
+			assert.NoError(t, err)
 			registerTextElementWithGarbage(fromPos, toPos, root, text)
 			assert.Equal(t, tc.want, text.String())
 			assert.Equal(t, tc.garbage, root.GarbageLen())
@@ -131,19 +136,22 @@ func TestRoot(t *testing.T) {
 		text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket())
 
 		fromPos, toPos := text.CreateRange(0, 0)
-		text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		_, _, err := text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, `[{"val":"Hello World"}]`, text.Marshal())
 		assert.Equal(t, 0, root.GarbageLen())
 
 		fromPos, toPos = text.CreateRange(6, 11)
-		text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, `[{"val":"Hello "},{"val":"Yorkie"}]`, text.Marshal())
 		assert.Equal(t, 1, root.GarbageLen())
 
 		fromPos, toPos = text.CreateRange(0, 6)
-		text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		registerTextElementWithGarbage(fromPos, toPos, root, text)
 		assert.Equal(t, `[{"val":"Yorkie"}]`, text.Marshal())
 		assert.Equal(t, 2, root.GarbageLen())
diff --git a/pkg/document/crdt/text_test.go b/pkg/document/crdt/text_test.go
index 04fa873c..92b4083f 100644
--- a/pkg/document/crdt/text_test.go
+++ b/pkg/document/crdt/text_test.go
@@ -32,11 +32,13 @@ func TestText(t *testing.T) {
 		text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket())
 
 		fromPos, toPos := text.CreateRange(0, 0)
-		text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		_, _, err := text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		assert.Equal(t, `[{"val":"Hello World"}]`, text.Marshal())
 
 		fromPos, toPos = text.CreateRange(6, 11)
-		text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		assert.Equal(t, `[{"val":"Hello "},{"val":"Yorkie"}]`, text.Marshal())
 	})
 
@@ -65,19 +67,21 @@ func TestText(t *testing.T) {
 	t.Run("marshal test", func(t *testing.T) {
 		root := helper.TestRoot()
 		ctx := helper.TextChangeContext(root)
-
 		text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket())
 
 		fromPos, toPos := text.CreateRange(0, 0)
-		text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		_, _, err := text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		assert.Equal(t, `[{"val":"Hello World"}]`, text.Marshal())
 
 		fromPos, toPos = text.CreateRange(6, 11)
-		text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		_, _, err = text.Edit(fromPos, toPos, nil, "Yorkie", nil, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		assert.Equal(t, `[{"val":"Hello "},{"val":"Yorkie"}]`, text.Marshal())
 
 		fromPos, toPos = text.CreateRange(0, 1)
-		text.Style(fromPos, toPos, map[string]string{"b": "1"}, ctx.IssueTimeTicket())
+		err = text.Style(fromPos, toPos, map[string]string{"b": "1"}, ctx.IssueTimeTicket())
+		assert.NoError(t, err)
 		assert.Equal(
 			t,
 			`[{"attrs":{"b":"1"},"val":"H"},{"val":"ello "},{"val":"Yorkie"}]`,