diff --git a/deploy/docker-compose/docker-compose.yml b/deploy/docker-compose/docker-compose.yml index 9104d6aab..664335653 100644 --- a/deploy/docker-compose/docker-compose.yml +++ b/deploy/docker-compose/docker-compose.yml @@ -1,12 +1,12 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy of this # software and associated documentation files (the "Software"), to deal in the Software # without restriction, including without limitation the rights to use, copy, modify, # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT @@ -19,7 +19,7 @@ version: '2' services: ui: ports: - - 8888:8080 + - 8888:8080 environment: - JAVA_OPTS=-XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom - SERVER_TOMCAT_ACCESSLOG_ENABLED=true @@ -79,7 +79,7 @@ services: hostname: carts-db restart: always mem_limit: 256m - + orders: hostname: orders image: retail-store-sample-orders:${TAG:-latest} @@ -87,28 +87,31 @@ services: environment: - JAVA_OPTS=-XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom - SERVER_TOMCAT_ACCESSLOG_ENABLED=true - - SPRING_PROFILES_ACTIVE=mysql,rabbitmq - - SPRING_DATASOURCE_WRITER_URL=jdbc:mariadb://orders-db:3306/orders - - SPRING_DATASOURCE_WRITER_USERNAME=orders_user - - SPRING_DATASOURCE_WRITER_PASSWORD=${MYSQL_PASSWORD} - - SPRING_DATASOURCE_READER_URL=jdbc:mariadb://orders-db:3306/orders - - SPRING_DATASOURCE_READER_USERNAME=orders_user - - SPRING_DATASOURCE_READER_PASSWORD=${MYSQL_PASSWORD} + - SPRING_PROFILES_ACTIVE=rabbitmq + - SPRING_DATASOURCE_URL=jdbc:postgresql://orders-db:5432/orders + - SPRING_DATASOURCE_USERNAME=orders_user + - SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} - SPRING_RABBITMQ_HOST=rabbitmq mem_limit: 512m cap_drop: - ALL orders-db: - image: mariadb:10.9 + image: postgres:16.1 hostname: orders-db restart: always + security_opt: + - no-new-privileges:true environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_DATABASE=orders - - MYSQL_USER=orders_user - - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - reschedule=on-node-failure + - POSTGRES_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_DB=orders + - POSTGRES_USER=orders_user + healthcheck: + test: [ "CMD-SHELL", "pg_isready -d orders -U orders_user" ] + interval: 10s + timeout: 5s + retries: 30 mem_limit: 128m checkout: @@ -145,4 +148,4 @@ services: image: rabbitmq:3-management ports: - "5672:5672" - - "15672:15672" \ No newline at end of file + - "15672:15672" diff --git a/deploy/kubernetes/charts/orders/templates/_helpers.tpl b/deploy/kubernetes/charts/orders/templates/_helpers.tpl index 3a9ad8fdc..1c5c6c6ae 100644 --- a/deploy/kubernetes/charts/orders/templates/_helpers.tpl +++ b/deploy/kubernetes/charts/orders/templates/_helpers.tpl @@ -86,16 +86,16 @@ Create the name of the config map to use {{- end }} {{- end -}} -{{- define "orders.mysql.fullname" -}} -{{- include "orders.fullname" . }}-mysql +{{- define "orders.postgresql.fullname" -}} +{{- include "orders.fullname" . }}-postgresql {{- end -}} {{/* -Common labels for mysql +Common labels for postgresql */}} -{{- define "orders.mysql.labels" -}} +{{- define "orders.postgresql.labels" -}} helm.sh/chart: {{ include "orders.chart" . }} -{{ include "orders.mysql.selectorLabels" . }} +{{ include "orders.postgresql.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -103,12 +103,12 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* -Selector labels for mysql +Selector labels for postgresql */}} -{{- define "orders.mysql.selectorLabels" -}} +{{- define "orders.postgresql.selectorLabels" -}} app.kubernetes.io/name: {{ include "orders.fullname" . }} app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/component: mysql +app.kubernetes.io/component: postgresql {{- end }} {{- define "getOrGeneratePass" }} @@ -123,35 +123,19 @@ app.kubernetes.io/component: mysql {{- end -}} {{- end }} -{{- define "orders.mysql.password" -}} -{{- if not (empty .Values.mysql.secret.password) -}} - {{- .Values.mysql.secret.password | b64enc -}} +{{- define "orders.postgresql.password" -}} +{{- if not (empty .Values.postgresql.secret.password) -}} + {{- .Values.postgresql.secret.password | b64enc -}} {{- else -}} - {{- include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" .Values.mysql.secret.name "Key" "password") -}} + {{- include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" .Values.postgresql.secret.name "Key" "password") -}} {{- end -}} {{- end -}} -{{- define "orders.mysql.reader.password" -}} -{{- if not (empty .Values.mysql.reader.secret.password) -}} - {{- .Values.mysql.reader.secret.password | b64enc -}} +{{- define "orders.postgresql.endpoint" -}} +{{- if not (empty .Values.postgresql.endpoint.host) -}} +jdbc:postgresql://{{- .Values.postgresql.endpoint.host -}}:{{- .Values.postgresql.endpoint.port -}}/{{ .Values.postgresql.database }} {{- else -}} - {{- include "getOrGeneratePass" (dict "Namespace" .Release.Namespace "Kind" "Secret" "Name" .Values.mysql.reader.secret.name "Key" "password") -}} -{{- end -}} -{{- end -}} - -{{- define "orders.mysql.endpoint" -}} -{{- if not (empty .Values.mysql.endpoint) -}} - {{- .Values.mysql.endpoint -}} -{{- else -}} -jdbc:mariadb://{{ include "orders.mysql.fullname" . }}:{{ .Values.mysql.service.port }}/{{ .Values.mysql.database }} -{{- end -}} -{{- end -}} - -{{- define "orders.mysql.reader.endpoint" -}} -{{- if not (empty .Values.mysql.reader.endpoint) -}} - {{- .Values.mysql.reader.endpoint -}} -{{- else -}} -{{- include "orders.mysql.endpoint" . -}} +jdbc:postgresql://{{ include "orders.postgresql.fullname" . }}:{{ .Values.postgresql.service.port }}/{{ .Values.postgresql.database }} {{- end -}} {{- end -}} diff --git a/deploy/kubernetes/charts/orders/templates/configmap.yml b/deploy/kubernetes/charts/orders/templates/configmap.yml index 179f90799..ab3f8f0f6 100644 --- a/deploy/kubernetes/charts/orders/templates/configmap.yml +++ b/deploy/kubernetes/charts/orders/templates/configmap.yml @@ -4,8 +4,7 @@ kind: ConfigMap metadata: name: {{ include "orders.configMapName" . }} data: - SPRING_PROFILES_ACTIVE: mysql,rabbitmq - SPRING_DATASOURCE_READER_URL: {{ include "orders.mysql.endpoint" . }} - SPRING_DATASOURCE_WRITER_URL: {{ include "orders.mysql.reader.endpoint" . }} + SPRING_PROFILES_ACTIVE: rabbitmq + SPRING_DATASOURCE_URL: {{ include "orders.postgresql.endpoint" . }} SPRING_RABBITMQ_ADDRESSES: {{ include "orders.rabbitmq.addresses" . }} {{- end }} diff --git a/deploy/kubernetes/charts/orders/templates/deployment.yaml b/deploy/kubernetes/charts/orders/templates/deployment.yaml index 3eb5bb802..f28f23ff9 100644 --- a/deploy/kubernetes/charts/orders/templates/deployment.yaml +++ b/deploy/kubernetes/charts/orders/templates/deployment.yaml @@ -39,25 +39,15 @@ spec: env: - name: JAVA_OPTS value: -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom - - name: SPRING_DATASOURCE_WRITER_USERNAME + - name: SPRING_DATASOURCE_USERNAME valueFrom: secretKeyRef: - name: {{ .Values.mysql.secret.name }} + name: {{ .Values.postgresql.secret.name }} key: username - - name: SPRING_DATASOURCE_WRITER_PASSWORD + - name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: - name: {{ .Values.mysql.secret.name }} - key: password - - name: SPRING_DATASOURCE_READER_USERNAME - valueFrom: - secretKeyRef: - name: {{ .Values.mysql.reader.secret.name }} - key: username - - name: SPRING_DATASOURCE_READER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.mysql.reader.secret.name }} + name: {{ .Values.postgresql.secret.name }} key: password envFrom: - secretRef: diff --git a/deploy/kubernetes/charts/orders/templates/mysql-reader-secret.yaml b/deploy/kubernetes/charts/orders/templates/mysql-reader-secret.yaml deleted file mode 100644 index 17f2a1ff0..000000000 --- a/deploy/kubernetes/charts/orders/templates/mysql-reader-secret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if .Values.mysql.reader.secret.create }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.mysql.reader.secret.name }} -data: - username: {{ .Values.mysql.reader.secret.username | b64enc | quote }} - password: "{{ include "orders.mysql.reader.password" . }}" -{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/mysql-secret.yaml b/deploy/kubernetes/charts/orders/templates/mysql-secret.yaml deleted file mode 100644 index 44faebc4d..000000000 --- a/deploy/kubernetes/charts/orders/templates/mysql-secret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if .Values.mysql.secret.create }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.mysql.secret.name }} -data: - username: {{ .Values.mysql.secret.username | b64enc | quote }} - password: "{{ include "orders.mysql.password" . }}" -{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/mysql-service.yaml b/deploy/kubernetes/charts/orders/templates/mysql-service.yaml deleted file mode 100644 index 3bd08918f..000000000 --- a/deploy/kubernetes/charts/orders/templates/mysql-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.mysql.create }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "orders.mysql.fullname" . }} - labels: - {{- include "orders.mysql.labels" . | nindent 4 }} -spec: - type: {{ .Values.mysql.service.type }} - ports: - - port: {{ .Values.mysql.service.port }} - targetPort: mysql - protocol: TCP - name: mysql - selector: - {{- include "orders.mysql.selectorLabels" . | nindent 4 }} -{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/mysql-statefulset.yaml b/deploy/kubernetes/charts/orders/templates/mysql-statefulset.yaml deleted file mode 100644 index eaf85423b..000000000 --- a/deploy/kubernetes/charts/orders/templates/mysql-statefulset.yaml +++ /dev/null @@ -1,97 +0,0 @@ -{{- if .Values.mysql.create }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "orders.mysql.fullname" . }} - labels: - {{- include "orders.mysql.labels" . | nindent 4 }} -spec: - replicas: 1 - serviceName: {{ include "orders.mysql.fullname" . }} - selector: - matchLabels: - {{- include "orders.mysql.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.mysql.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "orders.mysql.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: mysql - image: "{{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }}" - imagePullPolicy: {{ .Values.mysql.image.pullPolicy }} - env: - - name: MYSQL_ROOT_PASSWORD - value: my-secret-pw - - name: MYSQL_DATABASE - value: {{ .Values.mysql.database }} - - name: MYSQL_USER - valueFrom: - secretKeyRef: - name: {{ .Values.mysql.secret.name }} - key: username - - name: MYSQL_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.mysql.secret.name }} - key: password - args: - - "--ignore-db-dir=lost+found" - volumeMounts: - - name: data - mountPath: /var/lib/mysql - ports: - - name: mysql - containerPort: 3306 - protocol: TCP - {{- with .Values.mysql.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.mysql.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.mysql.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- if .Values.mysql.persistentVolume.enabled }} - volumeClaimTemplates: - - metadata: - name: data - {{- if .Values.mysql.persistentVolume.annotations }} - annotations: -{{ toYaml .Values.mysql.persistentVolume.annotations | indent 10 }} - {{- end }} - {{- if .Values.mysql.persistentVolume.labels }} - labels: -{{ toYaml .Values.mysql.persistentVolume.labels | indent 10 }} - {{- end }} - spec: - accessModes: -{{ toYaml .Values.mysql.persistentVolume.accessModes | indent 8 }} - resources: - requests: - storage: "{{ .Values.mysql.persistentVolume.size }}" - {{- if .Values.mysql.persistentVolume.storageClass }} - {{- if (eq "-" .Values.mysql.persistentVolume.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: "{{ .Values.mysql.persistentVolume.storageClass }}" - {{- end }} - {{- end }} -{{- else }} - volumes: - - name: data - emptyDir: {} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/postgresql-secret.yaml b/deploy/kubernetes/charts/orders/templates/postgresql-secret.yaml new file mode 100644 index 000000000..e088e59c5 --- /dev/null +++ b/deploy/kubernetes/charts/orders/templates/postgresql-secret.yaml @@ -0,0 +1,9 @@ +{{- if .Values.postgresql.secret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.postgresql.secret.name }} +data: + username: {{ .Values.postgresql.secret.username | b64enc | quote }} + password: "{{ include "orders.postgresql.password" . }}" +{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/postgresql-service.yaml b/deploy/kubernetes/charts/orders/templates/postgresql-service.yaml new file mode 100644 index 000000000..fd9c780b1 --- /dev/null +++ b/deploy/kubernetes/charts/orders/templates/postgresql-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.postgresql.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "orders.postgresql.fullname" . }} + labels: + {{- include "orders.postgresql.labels" . | nindent 4 }} +spec: + type: {{ .Values.postgresql.service.type }} + ports: + - port: {{ .Values.postgresql.service.port }} + targetPort: postgresql + protocol: TCP + name: postgresql + selector: + {{- include "orders.postgresql.selectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/templates/postgresql-statefulset.yaml b/deploy/kubernetes/charts/orders/templates/postgresql-statefulset.yaml new file mode 100644 index 000000000..9f93a2f02 --- /dev/null +++ b/deploy/kubernetes/charts/orders/templates/postgresql-statefulset.yaml @@ -0,0 +1,95 @@ +{{- if .Values.postgresql.create }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "orders.postgresql.fullname" . }} + labels: + {{- include "orders.postgresql.labels" . | nindent 4 }} +spec: + replicas: 1 + serviceName: {{ include "orders.postgresql.fullname" . }} + selector: + matchLabels: + {{- include "orders.postgresql.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.postgresql.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "orders.postgresql.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: postgresql + image: "{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}" + imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }} + env: + - name: POSTGRES_DB + value: {{ .Values.postgresql.database }} + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.secret.name }} + key: username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.secret.name }} + key: password + - name: PGDATA + value: /data/pgdata + volumeMounts: + - name: data + mountPath: /data + ports: + - name: postgresql + containerPort: 5432 + protocol: TCP + {{- with .Values.postgresql.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.postgresql.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.postgresql.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.postgresql.persistentVolume.enabled }} + volumeClaimTemplates: + - metadata: + name: data + {{- if .Values.postgresql.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.postgresql.persistentVolume.annotations | indent 10 }} + {{- end }} + {{- if .Values.postgresql.persistentVolume.labels }} + labels: +{{ toYaml .Values.postgresql.persistentVolume.labels | indent 10 }} + {{- end }} + spec: + accessModes: +{{ toYaml .Values.postgresql.persistentVolume.accessModes | indent 8 }} + resources: + requests: + storage: "{{ .Values.postgresql.persistentVolume.size }}" + {{- if .Values.postgresql.persistentVolume.storageClass }} + {{- if (eq "-" .Values.postgresql.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.postgresql.persistentVolume.storageClass }}" + {{- end }} + {{- end }} +{{- else }} + volumes: + - name: data + emptyDir: {} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/charts/orders/values.yaml b/deploy/kubernetes/charts/orders/values.yaml index ea30cb748..5df511981 100644 --- a/deploy/kubernetes/charts/orders/values.yaml +++ b/deploy/kubernetes/charts/orders/values.yaml @@ -70,12 +70,14 @@ configMap: create: true name: -mysql: +postgresql: create: true database: orders - endpoint: "" + endpoint: + host: "" + port: "" secret: create: true @@ -92,13 +94,13 @@ mysql: password: "" image: - repository: mysql + repository: postgres pullPolicy: IfNotPresent - tag: "5.7" + tag: "16.1" service: type: ClusterIP - port: 3306 + port: 5432 podAnnotations: {} diff --git a/deploy/kubernetes/charts/templates/orders.yaml.gotmpl b/deploy/kubernetes/charts/templates/orders.yaml.gotmpl index 163df87a2..6b36ab44a 100644 --- a/deploy/kubernetes/charts/templates/orders.yaml.gotmpl +++ b/deploy/kubernetes/charts/templates/orders.yaml.gotmpl @@ -5,7 +5,7 @@ image: {{end}} {{if env "RANDOM_PASSWORD" }} -mysql: +postgresql: secret: password: {{ env "RANDOM_PASSWORD" | default "" }} {{end}} \ No newline at end of file diff --git a/deploy/terraform/eks/default/kubernetes.tf b/deploy/terraform/eks/default/kubernetes.tf index 8818b1713..f733786c5 100644 --- a/deploy/terraform/eks/default/kubernetes.tf +++ b/deploy/terraform/eks/default/kubernetes.tf @@ -46,7 +46,7 @@ resource "time_sleep" "workloads" { create_duration = "30s" destroy_duration = "60s" - depends_on = [ + depends_on = [ null_resource.addons_blocker ] } @@ -64,12 +64,12 @@ resource "kubernetes_namespace_v1" "assets" { } resource "helm_release" "assets" { - name = "assets" - chart = "../../../kubernetes/charts/assets" + name = "assets" + chart = "../../../kubernetes/charts/assets" - namespace = kubernetes_namespace_v1.assets.metadata[0].name + namespace = kubernetes_namespace_v1.assets.metadata[0].name values = [ - templatefile("${path.module}/values/assets.yaml", { + templatefile("${path.module}/values/assets.yaml", { opentelemetry_enabled = var.opentelemetry_enabled }) ] @@ -88,13 +88,13 @@ resource "kubernetes_namespace_v1" "catalog" { } resource "helm_release" "catalog" { - name = "catalog" - chart = "../../../kubernetes/charts/catalog" + name = "catalog" + chart = "../../../kubernetes/charts/catalog" - namespace = kubernetes_namespace_v1.catalog.metadata[0].name + namespace = kubernetes_namespace_v1.catalog.metadata[0].name values = [ - templatefile("${path.module}/values/catalog.yaml", { + templatefile("${path.module}/values/catalog.yaml", { opentelemetry_enabled = var.opentelemetry_enabled database_endpoint = "${module.dependencies.catalog_db_endpoint}:${module.dependencies.catalog_db_port}" database_username = module.dependencies.catalog_db_master_username @@ -117,16 +117,16 @@ resource "kubernetes_namespace_v1" "carts" { } resource "helm_release" "carts" { - name = "carts" - chart = "../../../kubernetes/charts/carts" + name = "carts" + chart = "../../../kubernetes/charts/carts" - namespace = kubernetes_namespace_v1.carts.metadata[0].name + namespace = kubernetes_namespace_v1.carts.metadata[0].name values = [ - templatefile("${path.module}/values/carts.yaml", { + templatefile("${path.module}/values/carts.yaml", { opentelemetry_enabled = var.opentelemetry_enabled role_arn = module.iam_assumable_role_carts.iam_role_arn - table_name = module.dependencies.carts_dynamodb_table_name + table_name = module.dependencies.carts_dynamodb_table_name }) ] } @@ -144,13 +144,13 @@ resource "kubernetes_namespace_v1" "checkout" { } resource "helm_release" "checkout" { - name = "checkout" - chart = "../../../kubernetes/charts/checkout" + name = "checkout" + chart = "../../../kubernetes/charts/checkout" - namespace = kubernetes_namespace_v1.checkout.metadata[0].name + namespace = kubernetes_namespace_v1.checkout.metadata[0].name values = [ - templatefile("${path.module}/values/checkout.yaml", { + templatefile("${path.module}/values/checkout.yaml", { opentelemetry_enabled = var.opentelemetry_enabled redis_address = module.dependencies.checkout_elasticache_primary_endpoint redis_port = module.dependencies.checkout_elasticache_port @@ -172,21 +172,23 @@ resource "kubernetes_namespace_v1" "orders" { } resource "helm_release" "orders" { - name = "orders" - chart = "../../../kubernetes/charts/orders" + name = "orders" + chart = "../../../kubernetes/charts/orders" - namespace = kubernetes_namespace_v1.orders.metadata[0].name + namespace = kubernetes_namespace_v1.orders.metadata[0].name values = [ - templatefile("${path.module}/values/orders.yaml", { - opentelemetry_enabled = var.opentelemetry_enabled - database_endpoint = "jdbc:mariadb://${module.dependencies.orders_db_endpoint}:${module.dependencies.orders_db_port}/${module.dependencies.orders_db_database_name}" - database_username = module.dependencies.orders_db_master_username - database_password = module.dependencies.orders_db_master_password - rabbitmq_endpoint = module.dependencies.mq_broker_endpoint - rabbitmq_username = module.dependencies.mq_user - rabbitmq_password = module.dependencies.mq_password - security_group_id = aws_security_group.orders.id + templatefile("${path.module}/values/orders.yaml", { + opentelemetry_enabled = var.opentelemetry_enabled + database_endpoint_host = module.dependencies.orders_db_endpoint + database_endpoint_port = module.dependencies.orders_db_port + database_name = module.dependencies.orders_db_database_name + database_username = module.dependencies.orders_db_master_username + database_password = module.dependencies.orders_db_master_password + rabbitmq_endpoint = module.dependencies.mq_broker_endpoint + rabbitmq_username = module.dependencies.mq_user + rabbitmq_password = module.dependencies.mq_password + security_group_id = aws_security_group.orders.id }) ] } @@ -204,10 +206,10 @@ resource "kubernetes_namespace_v1" "ui" { } resource "helm_release" "ui" { - name = "ui" - chart = "../../../kubernetes/charts/ui" + name = "ui" + chart = "../../../kubernetes/charts/ui" - namespace = kubernetes_namespace_v1.ui.metadata[0].name + namespace = kubernetes_namespace_v1.ui.metadata[0].name values = [ templatefile("${path.module}/values/ui.yaml", { @@ -220,23 +222,23 @@ resource "helm_release" "ui" { resource "time_sleep" "restart_pods" { create_duration = "30s" - depends_on = [ + depends_on = [ helm_release.ui, helm_release.opentelemetry ] } resource "null_resource" "restart_pods" { - depends_on = [ time_sleep.restart_pods ] + depends_on = [time_sleep.restart_pods] provisioner "local-exec" { interpreter = ["/bin/bash", "-c"] environment = { KUBECONFIG = base64encode(local.kubeconfig) } - + command = <<-EOT kubectl delete pod -A -l app.kuberneres.io/owner=retail-store-sample --kubeconfig <(echo $KUBECONFIG | base64 -d) EOT } -} \ No newline at end of file +} diff --git a/deploy/terraform/eks/default/values/orders.yaml b/deploy/terraform/eks/default/values/orders.yaml index 103fc9fbc..185c4d93a 100644 --- a/deploy/terraform/eks/default/values/orders.yaml +++ b/deploy/terraform/eks/default/values/orders.yaml @@ -1,7 +1,11 @@ -mysql: +postgresql: create: false - endpoint: ${database_endpoint} + database: ${database_name} + + endpoint: + host: ${database_endpoint_host} + port: "${database_endpoint_port}" secret: username: ${database_username} diff --git a/deploy/terraform/lib/apprunner/orders.tf b/deploy/terraform/lib/apprunner/orders.tf index ccedda7f1..cff7e73dc 100644 --- a/deploy/terraform/lib/apprunner/orders.tf +++ b/deploy/terraform/lib/apprunner/orders.tf @@ -1,21 +1,18 @@ resource "aws_apprunner_service" "orders" { service_name = "${var.environment_name}-orders" - + source_configuration { auto_deployments_enabled = false image_repository { image_configuration { port = 8080 runtime_environment_secrets = { - SPRING_DATASOURCE_WRITER_URL = "${aws_secretsmanager_secret.orders_db.arn}:host::" - SPRING_DATASOURCE_WRITER_USERNAME = "${aws_secretsmanager_secret.orders_db.arn}:username::" - SPRING_DATASOURCE_WRITER_PASSWORD = "${aws_secretsmanager_secret.orders_db.arn}:password::" - SPRING_DATASOURCE_READER_URL = "${aws_secretsmanager_secret.orders_db.arn}:host::" - SPRING_DATASOURCE_READER_USERNAME = "${aws_secretsmanager_secret.orders_db.arn}:username::" - SPRING_DATASOURCE_READER_PASSWORD = "${aws_secretsmanager_secret.orders_db.arn}:password::" - SPRING_RABBITMQ_ADDRESSES = "${aws_secretsmanager_secret.mq.arn}:host::" - SPRING_RABBITMQ_USER = "${aws_secretsmanager_secret.mq.arn}:username::" - SPRING_RABBITMQ_PASSWORD = "${aws_secretsmanager_secret.mq.arn}:password::" + SPRING_DATASOURCE_URL = "${aws_secretsmanager_secret.orders_db.arn}:host::" + SPRING_DATASOURCE_USERNAME = "${aws_secretsmanager_secret.orders_db.arn}:username::" + SPRING_DATASOURCE_PASSWORD = "${aws_secretsmanager_secret.orders_db.arn}:password::" + SPRING_RABBITMQ_ADDRESSES = "${aws_secretsmanager_secret.mq.arn}:host::" + SPRING_RABBITMQ_USER = "${aws_secretsmanager_secret.mq.arn}:username::" + SPRING_RABBITMQ_PASSWORD = "${aws_secretsmanager_secret.mq.arn}:password::" } } image_identifier = module.container_images.result.orders @@ -59,8 +56,8 @@ resource "aws_apprunner_vpc_ingress_connection" "orders" { } resource "random_string" "random_orders_secret" { - length = 4 - special = false + length = 4 + special = false } resource "aws_secretsmanager_secret" "orders_db" { @@ -106,7 +103,7 @@ data "aws_iam_policy_document" "orders_db_secret" { "secretsmanager:GetSecretValue", "kms:Decrypt*" ] - effect = "Allow" + effect = "Allow" resources = [ aws_secretsmanager_secret.orders_db.arn, aws_secretsmanager_secret.mq.arn, @@ -135,4 +132,4 @@ resource "aws_security_group" "orders" { } tags = var.tags -} \ No newline at end of file +} diff --git a/deploy/terraform/lib/dependencies/orders_rds.tf b/deploy/terraform/lib/dependencies/orders_rds.tf index 219cbcfb6..95385a76a 100644 --- a/deploy/terraform/lib/dependencies/orders_rds.tf +++ b/deploy/terraform/lib/dependencies/orders_rds.tf @@ -2,16 +2,16 @@ module "orders_rds" { source = "terraform-aws-modules/rds-aurora/aws" version = "7.7.1" - name = "${var.environment_name}-orders" - engine = "aurora-mysql" - engine_version = "5.7" - instance_class = "db.t3.small" + name = "${var.environment_name}-orders" + engine = "aurora-postgresql" + engine_version = "15.5" + instance_class = "db.t3.medium" instances = { one = {} } - vpc_id = var.vpc_id + vpc_id = var.vpc_id subnets = var.subnet_ids allowed_security_groups = concat(var.allowed_security_group_ids, [var.orders_security_group_id]) @@ -25,16 +25,16 @@ module "orders_rds" { create_db_parameter_group = true db_parameter_group_name = "${var.environment_name}-orders" - db_parameter_group_family = "aurora-mysql5.7" + db_parameter_group_family = "aurora-postgresql15" create_db_cluster_parameter_group = true db_cluster_parameter_group_name = "${var.environment_name}-orders" - db_cluster_parameter_group_family = "aurora-mysql5.7" + db_cluster_parameter_group_family = "aurora-postgresql15" - tags = var.tags + tags = var.tags } resource "random_string" "orders_db_master" { length = 10 special = false -} \ No newline at end of file +} diff --git a/src/catalog/docker-compose.yml b/src/catalog/docker-compose.yml index f456e9b40..704e20080 100644 --- a/src/catalog/docker-compose.yml +++ b/src/catalog/docker-compose.yml @@ -10,7 +10,7 @@ services: image: microservices-demo/catalog hostname: catalog depends_on: - - catalog-db + - catalog-db restart: always cap_drop: - all diff --git a/src/orders/docker-compose.yml b/src/orders/docker-compose.yml index 4f5c7250c..0bb8d6af1 100644 --- a/src/orders/docker-compose.yml +++ b/src/orders/docker-compose.yml @@ -22,19 +22,19 @@ services: environment: - reschedule=on-node-failure - SERVER_TOMCAT_ACCESSLOG_ENABLED=true - - SPRING_PROFILES_ACTIVE=mysql,activemq - - SPRING_DATASOURCE_WRITER_URL=jdbc:mysql://orders-db:3306/orders - - SPRING_DATASOURCE_WRITER_USERNAME=orders_user - - SPRING_DATASOURCE_WRITER_PASSWORD=${MYSQL_PASSWORD} - - SPRING_DATASOURCE_READER_URL=jdbc:mysql://orders-db:3306/orders - - SPRING_DATASOURCE_READER_USERNAME=orders_user - - SPRING_DATASOURCE_READER_PASSWORD=${MYSQL_PASSWORD} - - SPRING_DATASOURCE_WRITER_MAXIMUMPOOLSIZE=20 + - SPRING_PROFILES_ACTIVE=rabbitmq + - SPRING_DATASOURCE_URL=jdbc:postgresql://orders-db:5432/orders + - SPRING_DATASOURCE_USERNAME=orders_user + - SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} - SPRING_RABBITMQ_HOST=rabbitmq ports: - "8083:8080" healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"] + test: + [ + "CMD-SHELL", + "curl -f http://localhost:8080/actuator/health || exit 1" + ] interval: 10s timeout: 10s retries: 3 @@ -42,24 +42,22 @@ services: # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service orders-db: - image: mysql:5.7 + image: postgres:16.1 hostname: orders-db restart: always security_opt: - no-new-privileges:true environment: - reschedule=on-node-failure - - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_DATABASE=orders - - MYSQL_USER=orders_user - - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_DB=orders + - POSTGRES_USER=orders_user ports: - - "3306:3306" + - "5432:5432" healthcheck: - test: ["CMD-SHELL", "mysql -u root -p${MYSQL_PASSWORD} -e 'SELECT 1 cache'"] - interval: 1s - timeout: 3s + test: [ "CMD-SHELL", "pg_isready -d orders -U orders_user" ] + interval: 10s + timeout: 5s retries: 30 # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service @@ -72,4 +70,3 @@ services: ports: - "61616:61616" - "8161:8161" - diff --git a/src/orders/pom.xml b/src/orders/pom.xml index 53831d97d..a834eeea1 100644 --- a/src/orders/pom.xml +++ b/src/orders/pom.xml @@ -1,176 +1,189 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.2.1 - - - com.amazon.sample - orders - 0.0.1-SNAPSHOT - orders - Orders component + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.1 + + + com.amazon.sample + orders + 0.0.1-SNAPSHOT + orders + Orders component - - 17 - 1.5.5.Final - + + 17 + 1.5.5.Final + - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-properties-migrator - runtime - - - org.springframework.boot - spring-boot-starter-validation - runtime - - - io.micrometer - micrometer-registry-prometheus - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.mariadb.jdbc - mariadb-java-client - 3.3.2 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.springframework.boot - spring-boot-starter-log4j2 - - - org.springframework.boot - spring-boot-starter-amqp - - - org.springdoc - springdoc-openapi-ui - 1.7.0 - - - com.fasterxml.jackson.core - jackson-databind - 2.16.1 - + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-properties-migrator + runtime + + + io.micrometer + micrometer-registry-prometheus + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.flywaydb + flyway-core + + + org.postgresql + postgresql + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + io.rest-assured + rest-assured + test + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + - - org.mapstruct - mapstruct - ${mapstruct.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - provided - + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + - - com.h2database - h2 - runtime - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + - - - - org.springframework.boot - spring-boot-maven-plugin - - - - start - stop - - - - - - org.springdoc - springdoc-openapi-maven-plugin - 1.4 - - - integration-test - - generate - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.12.1 - - ${java.version} - ${java.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - org.projectlombok - lombok-mapstruct-binding - 0.2.0 - - - org.projectlombok - lombok - ${lombok.version} - - - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + start + stop + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.4 + + + integration-test + + generate + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + ${java.version} + ${java.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.projectlombok + lombok + ${lombok.version} + + + + + + diff --git a/src/orders/src/main/java/com/amazon/sample/orders/OrdersApplication.java b/src/orders/src/main/java/com/amazon/sample/orders/OrdersApplication.java index 8ee07e6a3..159c3cc40 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/OrdersApplication.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/OrdersApplication.java @@ -27,5 +27,4 @@ public class OrdersApplication { public static void main(String[] args) { SpringApplication.run(OrdersApplication.class, args); } - } diff --git a/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java b/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java new file mode 100644 index 000000000..2f5453d1a --- /dev/null +++ b/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java @@ -0,0 +1,35 @@ +package com.amazon.sample.orders.config; + +import com.amazon.sample.orders.entities.OrderEntity; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; +import org.springframework.data.relational.RelationalManagedTypes; +import org.springframework.data.relational.core.mapping.NamingStrategy; +import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; + +import java.util.Optional; +import java.util.UUID; + +@Configuration +public class PersistenceConfig extends AbstractJdbcConfiguration { + @Bean + BeforeConvertCallback beforeSaveCallback() { + return (entity) -> { + if (entity.getId() == null) { + entity.setId(UUID.randomUUID().toString()); + } + return entity; + }; + } + + @Override + public JdbcMappingContext jdbcMappingContext(Optional namingStrategy, + JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) { + JdbcMappingContext context = super.jdbcMappingContext(namingStrategy, customConversions, jdbcManagedTypes); + context.setForceQuote(false); + return context; + } +} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/ReaderConfig.java b/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/ReaderConfig.java deleted file mode 100644 index 0acde1140..000000000 --- a/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/ReaderConfig.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: MIT-0 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.amazon.sample.orders.config.persistence; - -import com.amazon.sample.orders.repositories.ReadOnlyRepository; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; -import org.springframework.context.annotation.*; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import jakarta.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -@Configuration -@Profile("mysql") -@EnableTransactionManagement -@EnableJpaRepositories( - entityManagerFactoryRef = "readerEntityManagerFactory", - transactionManagerRef = "readerTransactionManager", - includeFilters = @ComponentScan.Filter(ReadOnlyRepository.class), - basePackages = { - "com.amazon.sample.orders" - } -) -public class ReaderConfig { - - @Bean(name = "readerDataSource") - @ConfigurationProperties(prefix = "spring.datasource.reader") - public DataSource customerDataSource() { - return DataSourceBuilder.create().build(); - } - - @Bean(name = "readerEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean - entityManagerFactory( - EntityManagerFactoryBuilder builder, - @Qualifier("readerDataSource") DataSource dataSource - ) { - return builder - .dataSource(dataSource) - .packages("com.amazon.sample.orders") - .persistenceUnit("reader") - .build(); - } - - @Bean(name = "readerTransactionManager") - public PlatformTransactionManager customerTransactionManager( - @Qualifier("readerEntityManagerFactory") EntityManagerFactory customerEntityManagerFactory - ) { - return new JpaTransactionManager(customerEntityManagerFactory); - } -} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/WriterConfig.java b/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/WriterConfig.java deleted file mode 100644 index 09d100bae..000000000 --- a/src/orders/src/main/java/com/amazon/sample/orders/config/persistence/WriterConfig.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: MIT-0 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.amazon.sample.orders.config.persistence; - -import com.amazon.sample.orders.repositories.ReadOnlyRepository; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; -import org.springframework.context.annotation.*; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; - -import jakarta.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -@Configuration -@Profile("mysql") -@EnableTransactionManagement -@EnableJpaRepositories( - entityManagerFactoryRef = "writerEntityManagerFactory", - transactionManagerRef = "writerTransactionManager", - excludeFilters = @ComponentScan.Filter(ReadOnlyRepository.class), - basePackages = { - "com.amazon.sample.orders" - } -) -public class WriterConfig { - - @Primary - @Bean(name = "writerDataSource") - @ConfigurationProperties(prefix = "spring.datasource.writer") - public DataSource customerDataSource() { - return DataSourceBuilder.create().build(); - } - - @Primary - @Bean(name = "writerEntityManagerFactory") - public LocalContainerEntityManagerFactoryBean - entityManagerFactory( - EntityManagerFactoryBuilder builder, - @Qualifier("writerDataSource") DataSource dataSource - ) { - return builder - .dataSource(dataSource) - .packages("com.amazon.sample.orders") - .persistenceUnit("writer") - .build(); - } - - @Primary - @Bean(name = "writerTransactionManager") - public PlatformTransactionManager writerTransactionManager( - @Qualifier("writerEntityManagerFactory") EntityManagerFactory writerEntityManagerFactory - ) { - return new JpaTransactionManager(writerEntityManagerFactory); - } -} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java index 668d8d31e..04c652eab 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java @@ -18,30 +18,88 @@ package com.amazon.sample.orders.entities; -import lombok.Data; -import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.MappedCollection; +import org.springframework.data.relational.core.mapping.Table; -import jakarta.persistence.*; import java.util.ArrayList; import java.util.List; -@Entity -@Table(name="CUSTOMER_ORDER") -@Data +@Table public class OrderEntity { - @Id - @GeneratedValue(generator = "uuid") - @GenericGenerator(name = "uuid", strategy = "uuid2") private String id; private String firstName; private String lastName; private String email; - @OneToMany( - mappedBy = "order", - orphanRemoval = true, - fetch = FetchType.EAGER - ) + @MappedCollection(keyColumn = "product_id") private List items = new ArrayList<>(); + + public OrderEntity() { + + } + + public OrderEntity(String firstName, String lastName, String email) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public OrderEntity addItem(OrderItemEntity item) { + this.items.add(item); + + return this; + } + + @Override + public String toString() { + return "OrderEntity{" + + "id='" + id + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", items=" + items + + '}'; + } } \ No newline at end of file diff --git a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java index 329c6f366..dc921c7c2 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java @@ -18,42 +18,69 @@ package com.amazon.sample.orders.entities; -import lombok.Data; +import org.springframework.data.relational.core.mapping.Table; -import jakarta.persistence.*; -import java.io.Serializable; - -@Entity -@Table(name="CUSTOMER_ORDER_ITEM") -@Data +@Table public class OrderItemEntity { - @Embeddable - @Data - public static class Key implements Serializable { - private String orderId; - private String productId; - } - - @EmbeddedId - @AttributeOverrides({ - @AttributeOverride(name="productId", - column=@Column(length=64)) - }) - private Key id; - @Transient private String productId; private int quantity; - private int price; + private int unitCost; private String name; private int totalCost; - @ManyToOne(fetch = FetchType.LAZY) - @MapsId("orderId") + public String getProductId() { + return productId; + } + + public void setProductId(String productId) { + this.productId = productId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public int getUnitCost() { + return unitCost; + } - private OrderEntity order; + public void setUnitCost(int unitCost) { + this.unitCost = unitCost; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getTotalCost() { + return totalCost; + } + + public void setTotalCost(int totalCost) { + this.totalCost = totalCost; + } + + @Override + public String toString() { + return "OrderItemEntity{" + + ", productId='" + productId + '\'' + + ", quantity=" + quantity + + ", unitCost=" + unitCost + + ", name='" + name + '\'' + + ", totalCost=" + totalCost + + '}'; + } } diff --git a/src/orders/src/main/java/com/amazon/sample/orders/messaging/OrdersEventHandler.java b/src/orders/src/main/java/com/amazon/sample/orders/messaging/OrdersEventHandler.java index 35e2a6fd5..a7feed46c 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/messaging/OrdersEventHandler.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/messaging/OrdersEventHandler.java @@ -47,7 +47,7 @@ public void postCreatedEvent(OrderEntity entity) { order.setFirstName(entity.getFirstName()); order.setLastName(entity.getLastName()); order.setEmail(entity.getEmail()); - order.setOrderItems(entity.getItems()); + order.setOrderItems(entity.getItems().stream().toList()); OrderCreatedEvent event = new OrderCreatedEvent(); event.setOrder(order); diff --git a/src/orders/src/main/java/com/amazon/sample/orders/metrics/OrdersMetrics.java b/src/orders/src/main/java/com/amazon/sample/orders/metrics/OrdersMetrics.java index 4b4f6af54..a8b18c2b8 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/metrics/OrdersMetrics.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/metrics/OrdersMetrics.java @@ -21,7 +21,6 @@ import com.amazon.sample.events.orders.OrderCreatedEvent; import com.amazon.sample.orders.entities.OrderItemEntity; import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; diff --git a/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderReadRepository.java b/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderReadRepository.java deleted file mode 100644 index 80654179e..000000000 --- a/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderReadRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: MIT-0 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.amazon.sample.orders.repositories; - -import com.amazon.sample.orders.entities.OrderEntity; -import org.springframework.data.repository.CrudRepository; - -@ReadOnlyRepository -public interface OrderReadRepository extends CrudRepository { - -} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderRepository.java b/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderRepository.java index b40bd6278..d53eb2245 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderRepository.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/repositories/OrderRepository.java @@ -20,7 +20,9 @@ import com.amazon.sample.orders.entities.OrderEntity; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +@Repository public interface OrderRepository extends CrudRepository { } diff --git a/src/orders/src/main/java/com/amazon/sample/orders/repositories/ReadOnlyRepository.java b/src/orders/src/main/java/com/amazon/sample/orders/repositories/ReadOnlyRepository.java deleted file mode 100644 index 0589ece65..000000000 --- a/src/orders/src/main/java/com/amazon/sample/orders/repositories/ReadOnlyRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: MIT-0 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package com.amazon.sample.orders.repositories; - - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -public @interface ReadOnlyRepository { -} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/services/OrderService.java b/src/orders/src/main/java/com/amazon/sample/orders/services/OrderService.java index 13d0b8d2d..6b32acb87 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/services/OrderService.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/services/OrderService.java @@ -19,52 +19,42 @@ package com.amazon.sample.orders.services; import com.amazon.sample.orders.entities.OrderEntity; -import com.amazon.sample.orders.entities.OrderItemEntity; import com.amazon.sample.orders.messaging.OrdersEventHandler; -import com.amazon.sample.orders.repositories.OrderReadRepository; import com.amazon.sample.orders.repositories.OrderRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.relational.core.mapping.event.AbstractRelationalEventListener; +import org.springframework.data.relational.core.mapping.event.AfterSaveEvent; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import jakarta.transaction.Transactional; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @Service @Slf4j -public class OrderService { +public class OrderService extends AbstractRelationalEventListener { @Autowired private OrderRepository repository; - @Autowired - private OrderReadRepository readRepository; - @Autowired private OrdersEventHandler eventHandler; @Transactional public OrderEntity create(OrderEntity order) { - for(OrderItemEntity item : order.getItems()) { - item.setOrder(order); - - OrderItemEntity.Key key = new OrderItemEntity.Key(); - //key.setProductId(item.getProductId()); - - item.setId(key); - } - OrderEntity entity = repository.save(order); - eventHandler.postCreatedEvent(entity); - return entity; } public List list() { - return StreamSupport.stream(this.readRepository.findAll().spliterator(), false) - .collect(Collectors.toList()); + return StreamSupport.stream(this.repository.findAll().spliterator(), false) + .collect(Collectors.toList()); + } + + protected void onAfterSave(AfterSaveEvent orderCreated) { + this.eventHandler.postCreatedEvent(orderCreated.getEntity()); } } diff --git a/src/orders/src/main/java/com/amazon/sample/orders/web/OrderController.java b/src/orders/src/main/java/com/amazon/sample/orders/web/OrderController.java index c3959c7aa..cedcc1194 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/web/OrderController.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/web/OrderController.java @@ -18,6 +18,7 @@ package com.amazon.sample.orders.web; +import com.amazon.sample.orders.entities.OrderEntity; import com.amazon.sample.orders.services.OrderService; import com.amazon.sample.orders.web.payload.*; diff --git a/src/orders/src/main/java/com/amazon/sample/orders/web/payload/Order.java b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/Order.java index 50e4d9c81..9a13925f2 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/web/payload/Order.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/Order.java @@ -21,6 +21,7 @@ import lombok.Data; import java.util.List; +import java.util.Set; @Data public class Order { diff --git a/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderItemMapper.java b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderItemMapper.java new file mode 100644 index 000000000..a9fa1c836 --- /dev/null +++ b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderItemMapper.java @@ -0,0 +1,12 @@ +package com.amazon.sample.orders.web.payload; + +import com.amazon.sample.orders.entities.OrderItemEntity; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface OrderItemMapper { + + OrderItem toOrderItem(OrderItemEntity entity); + + OrderItemEntity toOrderItemEntity(OrderItem item); +} diff --git a/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderMapper.java b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderMapper.java index d0e9c876c..e796f6bb3 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderMapper.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/web/payload/OrderMapper.java @@ -22,15 +22,11 @@ import com.amazon.sample.orders.entities.OrderItemEntity; import org.mapstruct.Mapper; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", uses = OrderItemMapper.class) public interface OrderMapper { - Order toOrder(OrderEntity entity); - - OrderItem toOrderItem(OrderItemEntity entity); ExistingOrder toExistingOrder(OrderEntity entity); OrderEntity toOrderEntity(Order order); - - OrderItemEntity toOrderItemEntity(OrderItem item); } + diff --git a/src/orders/src/main/resources/application-mysql.yml b/src/orders/src/main/resources/application-mysql.yml deleted file mode 100644 index 7a5208999..000000000 --- a/src/orders/src/main/resources/application-mysql.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring: - jpa: - generate-ddl: true - show-sql: false - hibernate: - ddl-auto: update - - datasource: - writer: - jdbc-url: ${spring.datasource.writer.url} - username: - password: - driverClassName: org.mariadb.jdbc.Driver - - reader: - jdbc-url: ${spring.datasource.reader.url} - username: - password: - driverClassName: org.mariadb.jdbc.Driver \ No newline at end of file diff --git a/src/orders/src/main/resources/application.yml b/src/orders/src/main/resources/application.yml index b59a3fef9..98b6b50a1 100644 --- a/src/orders/src/main/resources/application.yml +++ b/src/orders/src/main/resources/application.yml @@ -5,4 +5,6 @@ management: include: '*' server: - port: ${port:8080} \ No newline at end of file + port: ${port:8080} + +spring.flyway.baseline-on-migrate: true \ No newline at end of file diff --git a/src/orders/src/main/resources/db/migration/V1__Initial.sql b/src/orders/src/main/resources/db/migration/V1__Initial.sql new file mode 100644 index 000000000..8b6f020da --- /dev/null +++ b/src/orders/src/main/resources/db/migration/V1__Initial.sql @@ -0,0 +1,16 @@ +create table IF NOT EXISTS order_entity ( + id varchar(255) not null, + first_name varchar(255) not null, + last_name varchar(255), + email varchar(255), + primary key (id) +); + +create table IF NOT EXISTS order_item_entity ( + product_id varchar(255) not null, + name varchar(255) not null, + quantity int, + unit_cost int, + total_cost int, + order_entity varchar(255) not null +); \ No newline at end of file diff --git a/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java b/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java similarity index 90% rename from src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java rename to src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java index bf5d245f7..ea499d18d 100644 --- a/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java +++ b/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java @@ -26,22 +26,18 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.stereotype.Component; -import org.springframework.transaction.event.TransactionalEventListener; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.BDDAssertions.then; - -public class OrdersMetricsTest { +public class OrdersMetricsTests { private MeterRegistry meterRegistry; private final String PRODUCT_1 = "Product1"; private final String PRODUCT_2 = "Product2"; + @BeforeEach void setUp() { meterRegistry = new SimpleMeterRegistry(); @@ -69,14 +65,14 @@ void testCreateCounterAndIncrement() { OrderItemEntity item = new OrderItemEntity(); item.setName("Pocket Watch"); item.setQuantity(5); - item.setPrice(100); + item.setUnitCost(100); item.setTotalCost(500); item.setProductId(PRODUCT_1); orderItems.add(item); item = new OrderItemEntity(); item.setName("Wood Watch"); item.setQuantity(2); - item.setPrice(50); + item.setUnitCost(50); item.setTotalCost(100); item.setProductId(PRODUCT_2); orderItems.add(item); @@ -85,15 +81,15 @@ void testCreateCounterAndIncrement() { event.setOrder(order); ordersMetrics.onOrderCreated(event); - var counter = meterRegistry.get("watch.orders").tags("productId","*").counter(); + var counter = meterRegistry.get("watch.orders").tags("productId", "*").counter(); then(counter).isNotNull(); then(counter.count()).isEqualTo(1); - var woodWatchCounter = meterRegistry.get("watch.orders").tags("productId",PRODUCT_2).counter(); + var woodWatchCounter = meterRegistry.get("watch.orders").tags("productId", PRODUCT_2).counter(); then(woodWatchCounter).isNotNull(); then(woodWatchCounter.count()).isEqualTo(2); - var pocketWatchCounter = meterRegistry.get("watch.orders").tags("productId",PRODUCT_1).counter(); + var pocketWatchCounter = meterRegistry.get("watch.orders").tags("productId", PRODUCT_1).counter(); then(pocketWatchCounter).isNotNull(); then(pocketWatchCounter.count()).isEqualTo(5); diff --git a/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java b/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java new file mode 100644 index 000000000..246e69317 --- /dev/null +++ b/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java @@ -0,0 +1,104 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.amazon.sample.orders.services; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.hasSize; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; + +import com.amazon.sample.orders.entities.OrderEntity; +import com.amazon.sample.orders.repositories.OrderRepository; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class OrderServicePostgresTests { + + @LocalServerPort + private Integer port; + + static PostgreSQLContainer postgres = new PostgreSQLContainer<>( + "postgres:16.1"); + + @BeforeAll + static void beforeAll() { + postgres.start(); + } + + @AfterAll + static void afterAll() { + postgres.stop(); + } + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgres::getJdbcUrl); + registry.add("spring.datasource.username", postgres::getUsername); + registry.add("spring.datasource.password", postgres::getPassword); + } + + @Autowired + OrderRepository orderRepository; + + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + port; + orderRepository.deleteAll(); + } + + @Test + void shouldGetEmptyOrders() { + given() + .contentType(ContentType.JSON) + .when() + .get("/orders") + .then() + .statusCode(200) + .body(".", hasSize(0)); + } + + @Test + void shouldGetAllOrders() { + List orders = List.of( + new OrderEntity("first", "last", "email@example.com"), + new OrderEntity("first", "last", "email@example.com")); + orderRepository.saveAll(orders); + + given() + .contentType(ContentType.JSON) + .when() + .get("/orders") + .then() + .statusCode(200) + .body(".", hasSize(2)); + } +} \ No newline at end of file