commit
69db1b0fa7
@ -1,3 +1 @@
|
|||||||
kubectl patch deployment -n ingress-nginx nginx-ingress-controller --type=json --patch="$(cat nginx-ingress\publish-service-patch.yaml)"
|
kubectl apply -f nginx-ingress\cloud-generic.yaml
|
||||||
kubectl apply -f nginx-ingress\azure\service.yaml
|
|
||||||
kubectl apply -f nginx-ingress\patch-service-without-rbac.yaml
|
|
2
k8s/deploy-ingress-dockerlocal.ps1
Normal file
2
k8s/deploy-ingress-dockerlocal.ps1
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
kubectl apply -f nginx-ingress\cm.yaml
|
||||||
|
kubectl apply -f nginx-ingress\cloud-generic.yaml
|
@ -1,12 +1,5 @@
|
|||||||
kubectl apply -f ingress.yaml
|
|
||||||
|
|
||||||
# Deploy nginx-ingress core files
|
# Deploy nginx-ingress core files
|
||||||
kubectl apply -f nginx-ingress\namespace.yaml
|
kubectl apply -f nginx-ingress\mandatory.yaml
|
||||||
kubectl apply -f nginx-ingress\default-backend.yaml
|
|
||||||
kubectl apply -f nginx-ingress\configmap.yaml
|
|
||||||
kubectl apply -f nginx-ingress\tcp-services-configmap.yaml
|
|
||||||
kubectl apply -f nginx-ingress\udp-services-configmap.yaml
|
|
||||||
kubectl apply -f nginx-ingress\without-rbac.yaml
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,6 +113,7 @@ ExecKube -cmd 'delete configmap internalurls'
|
|||||||
ExecKube -cmd 'delete configmap urls'
|
ExecKube -cmd 'delete configmap urls'
|
||||||
ExecKube -cmd 'delete configmap externalcfg'
|
ExecKube -cmd 'delete configmap externalcfg'
|
||||||
ExecKube -cmd 'delete configmap ocelot'
|
ExecKube -cmd 'delete configmap ocelot'
|
||||||
|
ExecKube -cmd 'delete -f ingress.yaml'
|
||||||
|
|
||||||
# start sql, rabbitmq, frontend deployments
|
# start sql, rabbitmq, frontend deployments
|
||||||
if ($deployInfrastructure) {
|
if ($deployInfrastructure) {
|
||||||
@ -204,5 +205,8 @@ ExecKube -cmd 'rollout resume deployments/apigwwm'
|
|||||||
ExecKube -cmd 'rollout resume deployments/apigwws'
|
ExecKube -cmd 'rollout resume deployments/apigwws'
|
||||||
ExecKube -cmd 'rollout resume deployments/ordering-signalrhub'
|
ExecKube -cmd 'rollout resume deployments/ordering-signalrhub'
|
||||||
|
|
||||||
|
Write-Host "Adding/Updating ingress resource..." -ForegroundColor Yellow
|
||||||
|
ExecKube -cmd 'apply -f ingress.yaml'
|
||||||
|
|
||||||
Write-Host "WebSPA is exposed at http://$externalDns, WebMVC at http://$externalDns/webmvc, WebStatus at http://$externalDns/webstatus" -ForegroundColor Yellow
|
Write-Host "WebSPA is exposed at http://$externalDns, WebMVC at http://$externalDns/webmvc, WebStatus at http://$externalDns/webstatus" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
18
k8s/helm-rbac.yaml
Normal file
18
k8s/helm-rbac.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: tiller
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: tiller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: tiller
|
||||||
|
namespace: kube-system
|
@ -8,11 +8,19 @@ Param(
|
|||||||
[parameter(Mandatory=$false)][bool]$clean=$true,
|
[parameter(Mandatory=$false)][bool]$clean=$true,
|
||||||
[parameter(Mandatory=$false)][string]$aksName="",
|
[parameter(Mandatory=$false)][string]$aksName="",
|
||||||
[parameter(Mandatory=$false)][string]$aksRg="",
|
[parameter(Mandatory=$false)][string]$aksRg="",
|
||||||
[parameter(Mandatory=$false)][string]$imageTag="latest"
|
[parameter(Mandatory=$false)][string]$imageTag="latest",
|
||||||
|
[parameter(Mandatory=$false)][bool]$useLocalk8s=$false
|
||||||
)
|
)
|
||||||
|
|
||||||
$dns = $externalDns
|
$dns = $externalDns
|
||||||
|
|
||||||
|
$ingressValuesFile="ingress_values.yaml"
|
||||||
|
|
||||||
|
if ($ingressValuesFile) {
|
||||||
|
$ingressValuesFile="ingress_values_dockerk8s.yaml"
|
||||||
|
$dns="localhost"
|
||||||
|
}
|
||||||
|
|
||||||
if ($externalDns -eq "aks") {
|
if ($externalDns -eq "aks") {
|
||||||
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
|
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
|
||||||
Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red
|
Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red
|
||||||
@ -58,18 +66,18 @@ $charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-a
|
|||||||
if ($deployInfrastructure) {
|
if ($deployInfrastructure) {
|
||||||
foreach ($infra in $infras) {
|
foreach ($infra in $infras) {
|
||||||
Write-Host "Installing infrastructure: $infra" -ForegroundColor Green
|
Write-Host "Installing infrastructure: $infra" -ForegroundColor Green
|
||||||
helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra
|
helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($chart in $charts) {
|
foreach ($chart in $charts) {
|
||||||
Write-Host "Installing: $chart" -ForegroundColor Green
|
Write-Host "Installing: $chart" -ForegroundColor Green
|
||||||
if ($useCustomRegistry) {
|
if ($useCustomRegistry) {
|
||||||
helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
|
helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed
|
if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed
|
||||||
helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
|
helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
k8s/helm/ingress_values_dockerk8s.yaml
Normal file
5
k8s/helm/ingress_values_dockerk8s.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ingress:
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: "nginx"
|
||||||
|
ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
@ -1,19 +0,0 @@
|
|||||||
kind: Service
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: ingress-nginx
|
|
||||||
namespace: ingress-nginx
|
|
||||||
labels:
|
|
||||||
app: ingress-nginx
|
|
||||||
spec:
|
|
||||||
externalTrafficPolicy: Local
|
|
||||||
type: LoadBalancer
|
|
||||||
selector:
|
|
||||||
app: ingress-nginx
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: http
|
|
||||||
- name: https
|
|
||||||
port: 443
|
|
||||||
targetPort: https
|
|
21
k8s/nginx-ingress/cloud-generic.yaml
Normal file
21
k8s/nginx-ingress/cloud-generic.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: ingress-nginx
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
spec:
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
type: LoadBalancer
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: http
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
targetPort: https
|
BIN
k8s/nginx-ingress/cm.yaml
Normal file
BIN
k8s/nginx-ingress/cm.yaml
Normal file
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
kind: ConfigMap
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: nginx-configuration
|
|
||||||
namespace: ingress-nginx
|
|
||||||
labels:
|
|
||||||
app: ingress-nginx
|
|
||||||
data:
|
|
||||||
ssl-redirect: "false"
|
|
||||||
proxy-buffer-size: "128k"
|
|
||||||
proxy-buffers: "4 256k"
|
|
@ -1,52 +0,0 @@
|
|||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: default-http-backend
|
|
||||||
labels:
|
|
||||||
app: default-http-backend
|
|
||||||
namespace: ingress-nginx
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: default-http-backend
|
|
||||||
spec:
|
|
||||||
terminationGracePeriodSeconds: 60
|
|
||||||
containers:
|
|
||||||
- name: default-http-backend
|
|
||||||
# Any image is permissable as long as:
|
|
||||||
# 1. It serves a 404 page at /
|
|
||||||
# 2. It serves 200 on a /healthz endpoint
|
|
||||||
image: gcr.io/google_containers/defaultbackend:1.4
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 8080
|
|
||||||
scheme: HTTP
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
timeoutSeconds: 5
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: 10m
|
|
||||||
memory: 20Mi
|
|
||||||
requests:
|
|
||||||
cpu: 10m
|
|
||||||
memory: 20Mi
|
|
||||||
---
|
|
||||||
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: default-http-backend
|
|
||||||
namespace: ingress-nginx
|
|
||||||
labels:
|
|
||||||
app: default-http-backend
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
selector:
|
|
||||||
app: default-http-backend
|
|
@ -0,0 +1,3 @@
|
|||||||
|
data:
|
||||||
|
mvc_e: http://10.0.75.1/webmvc
|
||||||
|
|
3
k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml
Normal file
3
k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
data:
|
||||||
|
urls__IdentityUrl: http://10.0.75.1/identity
|
||||||
|
urls__mvc: http://10.0.75.1/webmvc
|
39
k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml
Normal file
39
k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
labels:
|
||||||
|
app: webmvc
|
||||||
|
name: eshop-webmvc-loopback
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: webmvc
|
||||||
|
servicePort: http
|
||||||
|
path: /webmvc
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
kubernetes.io/ingress.class: nginx
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
labels:
|
||||||
|
app: identity-api
|
||||||
|
name: eshop-identity-api-loopback
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: identity
|
||||||
|
servicePort: http
|
||||||
|
path: /identity
|
238
k8s/nginx-ingress/mandatory.yaml
Normal file
238
k8s/nginx-ingress/mandatory.yaml
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: nginx-configuration
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-serviceaccount
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-clusterrole
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
- endpoints
|
||||||
|
- nodes
|
||||||
|
- pods
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- "extensions"
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- "extensions"
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-role
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
- pods
|
||||||
|
- secrets
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
resourceNames:
|
||||||
|
# Defaults to "<election-id>-<ingress-class>"
|
||||||
|
# Here: "<ingress-controller-leader>-<nginx>"
|
||||||
|
# This has to be adapted if you change either parameter
|
||||||
|
# when launching the nginx-ingress-controller.
|
||||||
|
- "ingress-controller-leader-nginx"
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- endpoints
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-role-nisa-binding
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: nginx-ingress-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: nginx-ingress-serviceaccount
|
||||||
|
namespace: ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-clusterrole-nisa-binding
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: nginx-ingress-clusterrole
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: nginx-ingress-serviceaccount
|
||||||
|
namespace: ingress-nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-controller
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
annotations:
|
||||||
|
prometheus.io/port: "10254"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: nginx-ingress-serviceaccount
|
||||||
|
containers:
|
||||||
|
- name: nginx-ingress-controller
|
||||||
|
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
|
||||||
|
args:
|
||||||
|
- /nginx-ingress-controller
|
||||||
|
- --configmap=$(POD_NAMESPACE)/nginx-configuration
|
||||||
|
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
|
||||||
|
- --annotations-prefix=nginx.ingress.kubernetes.io
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
# www-data -> 33
|
||||||
|
runAsUser: 33
|
||||||
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
- name: https
|
||||||
|
containerPort: 443
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10254
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10254
|
||||||
|
scheme: HTTP
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 1
|
@ -1,4 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: ingress-nginx
|
|
@ -1,40 +0,0 @@
|
|||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: nginx-ingress-controller
|
|
||||||
namespace: ingress-nginx
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: ingress-nginx
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: ingress-nginx
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx-ingress-controller
|
|
||||||
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
|
|
||||||
args:
|
|
||||||
- /nginx-ingress-controller
|
|
||||||
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
|
||||||
- --configmap=$(POD_NAMESPACE)/nginx-configuration
|
|
||||||
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
|
|
||||||
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
|
|
||||||
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
|
|
||||||
- --annotations-prefix=nginx.ingress.kubernetes.io
|
|
||||||
env:
|
|
||||||
- name: POD_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.name
|
|
||||||
- name: POD_NAMESPACE
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.namespace
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 80
|
|
||||||
- name: https
|
|
||||||
containerPort: 443
|
|
@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
'op': 'add',
|
|
||||||
'path': '/spec/template/spec/containers/0/args/-',
|
|
||||||
'value': '--publish-service=$(POD_NAMESPACE)/ingress-nginx'
|
|
||||||
}
|
|
||||||
]
|
|
22
k8s/nginx-ingress/service-nodeport.yaml
Normal file
22
k8s/nginx-ingress/service-nodeport.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ingress-nginx
|
||||||
|
namespace: ingress-nginx
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
targetPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: ingress-nginx
|
||||||
|
app.kubernetes.io/part-of: ingress-nginx
|
@ -1,5 +0,0 @@
|
|||||||
kind: ConfigMap
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: tcp-services
|
|
||||||
namespace: ingress-nginx
|
|
@ -1,5 +0,0 @@
|
|||||||
kind: ConfigMap
|
|
||||||
apiVersion: v1
|
|
||||||
metadata:
|
|
||||||
name: udp-services
|
|
||||||
namespace: ingress-nginx
|
|
@ -1,61 +0,0 @@
|
|||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: nginx-ingress-controller
|
|
||||||
namespace: ingress-nginx
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: ingress-nginx
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: ingress-nginx
|
|
||||||
annotations:
|
|
||||||
prometheus.io/port: '10254'
|
|
||||||
prometheus.io/scrape: 'true'
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx-ingress-controller
|
|
||||||
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
|
|
||||||
args:
|
|
||||||
- /nginx-ingress-controller
|
|
||||||
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
|
||||||
- --configmap=$(POD_NAMESPACE)/nginx-configuration
|
|
||||||
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
|
|
||||||
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
|
|
||||||
- --annotations-prefix=nginx.ingress.kubernetes.io
|
|
||||||
env:
|
|
||||||
- name: POD_NAME
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.name
|
|
||||||
- name: POD_NAMESPACE
|
|
||||||
valueFrom:
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: metadata.namespace
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 80
|
|
||||||
- name: https
|
|
||||||
containerPort: 443
|
|
||||||
livenessProbe:
|
|
||||||
failureThreshold: 3
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 10254
|
|
||||||
scheme: HTTP
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
successThreshold: 1
|
|
||||||
timeoutSeconds: 1
|
|
||||||
readinessProbe:
|
|
||||||
failureThreshold: 3
|
|
||||||
httpGet:
|
|
||||||
path: /healthz
|
|
||||||
port: 10254
|
|
||||||
scheme: HTTP
|
|
||||||
periodSeconds: 10
|
|
||||||
successThreshold: 1
|
|
||||||
timeoutSeconds: 1
|
|
@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||||
<PackageReference Include="Ocelot" Version="3.0.0" />
|
<PackageReference Include="Ocelot" Version="12.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CacheManager.Core;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
|
||||||
{
|
{
|
||||||
@ -10,7 +11,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
|
|||||||
CreationDate = DateTime.UtcNow;
|
CreationDate = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid Id { get; }
|
[JsonConstructor]
|
||||||
public DateTime CreationDate { get; }
|
public IntegrationEvent(Guid id, DateTime createDate)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
CreationDate = createDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
|
public Guid Id { get; private set; }
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
|
public DateTime CreationDate { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using System;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using System.Text;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||||
{
|
{
|
||||||
@ -37,9 +35,14 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
|||||||
where TH : IIntegrationEventHandler<T>
|
where TH : IIntegrationEventHandler<T>
|
||||||
{
|
{
|
||||||
var eventName = GetEventKey<T>();
|
var eventName = GetEventKey<T>();
|
||||||
|
|
||||||
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
|
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
|
||||||
|
|
||||||
|
if (!_eventTypes.Contains(typeof(T)))
|
||||||
|
{
|
||||||
_eventTypes.Add(typeof(T));
|
_eventTypes.Add(typeof(T));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
|
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
|
||||||
{
|
{
|
||||||
|
@ -217,14 +217,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
|||||||
if (subscription.IsDynamic)
|
if (subscription.IsDynamic)
|
||||||
{
|
{
|
||||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||||
|
if (handler == null) continue;
|
||||||
dynamic eventData = JObject.Parse(message);
|
dynamic eventData = JObject.Parse(message);
|
||||||
await handler.Handle(eventData);
|
await handler.Handle(eventData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||||
|
if (handler == null) continue;
|
||||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
|
||||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||||
}
|
}
|
||||||
|
@ -163,14 +163,16 @@
|
|||||||
if (subscription.IsDynamic)
|
if (subscription.IsDynamic)
|
||||||
{
|
{
|
||||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||||
|
if (handler == null) continue;
|
||||||
dynamic eventData = JObject.Parse(message);
|
dynamic eventData = JObject.Parse(message);
|
||||||
await handler.Handle(eventData);
|
await handler.Handle(eventData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||||
|
if (handler == null) continue;
|
||||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
|
||||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
|||||||
public enum EventStateEnum
|
public enum EventStateEnum
|
||||||
{
|
{
|
||||||
NotPublished = 0,
|
NotPublished = 0,
|
||||||
Published = 1,
|
InProgress = 1,
|
||||||
PublishedFailed = 2
|
Published = 2,
|
||||||
|
PublishedFailed = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System.Linq;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
||||||
{
|
{
|
||||||
@ -20,9 +23,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
|||||||
}
|
}
|
||||||
public Guid EventId { get; private set; }
|
public Guid EventId { get; private set; }
|
||||||
public string EventTypeName { get; private set; }
|
public string EventTypeName { get; private set; }
|
||||||
|
[NotMapped]
|
||||||
|
public string EventTypeShortName => EventTypeName.Split('.')?.Last();
|
||||||
|
[NotMapped]
|
||||||
|
public IntegrationEvent IntegrationEvent { get; private set; }
|
||||||
public EventStateEnum State { get; set; }
|
public EventStateEnum State { get; set; }
|
||||||
public int TimesSent { get; set; }
|
public int TimesSent { get; set; }
|
||||||
public DateTime CreationTime { get; private set; }
|
public DateTime CreationTime { get; private set; }
|
||||||
public string Content { get; private set; }
|
public string Content { get; private set; }
|
||||||
|
|
||||||
|
public IntegrationEventLogEntry DeserializeJsonContent(Type type)
|
||||||
|
{
|
||||||
|
IntegrationEvent = JsonConvert.DeserializeObject(Content, type) as IntegrationEvent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
|||||||
{
|
{
|
||||||
public interface IIntegrationEventLogService
|
public interface IIntegrationEventLogService
|
||||||
{
|
{
|
||||||
|
Task<IEnumerable<IntegrationEventLogEntry>> RetrieveEventLogsPendingToPublishAsync();
|
||||||
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction);
|
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction);
|
||||||
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
|
Task MarkEventAsPublishedAsync(Guid eventId);
|
||||||
|
Task MarkEventAsInProgressAsync(Guid eventId);
|
||||||
|
Task MarkEventAsFailedAsync(Guid eventId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
|
||||||
@ -12,6 +17,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
|||||||
{
|
{
|
||||||
private readonly IntegrationEventLogContext _integrationEventLogContext;
|
private readonly IntegrationEventLogContext _integrationEventLogContext;
|
||||||
private readonly DbConnection _dbConnection;
|
private readonly DbConnection _dbConnection;
|
||||||
|
private readonly List<Type> _eventTypes;
|
||||||
|
|
||||||
public IntegrationEventLogService(DbConnection dbConnection)
|
public IntegrationEventLogService(DbConnection dbConnection)
|
||||||
{
|
{
|
||||||
@ -21,6 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
|||||||
.UseSqlServer(_dbConnection)
|
.UseSqlServer(_dbConnection)
|
||||||
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
|
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
|
||||||
.Options);
|
.Options);
|
||||||
|
|
||||||
|
_eventTypes = Assembly.Load(Assembly.GetEntryAssembly().FullName)
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.Name.EndsWith(nameof(IntegrationEvent)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<IntegrationEventLogEntry>> RetrieveEventLogsPendingToPublishAsync()
|
||||||
|
{
|
||||||
|
return await _integrationEventLogContext.IntegrationEventLogs
|
||||||
|
.Where(e => e.State == EventStateEnum.NotPublished)
|
||||||
|
.OrderBy(o => o.CreationTime)
|
||||||
|
.Select(e => e.DeserializeJsonContent(_eventTypes.Find(t=> t.Name == e.EventTypeShortName)))
|
||||||
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction)
|
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction)
|
||||||
@ -38,11 +58,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
|||||||
return _integrationEventLogContext.SaveChangesAsync();
|
return _integrationEventLogContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task MarkEventAsPublishedAsync(IntegrationEvent @event)
|
public Task MarkEventAsPublishedAsync(Guid eventId)
|
||||||
{
|
{
|
||||||
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id);
|
return UpdateEventStatus(eventId, EventStateEnum.Published);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task MarkEventAsInProgressAsync(Guid eventId)
|
||||||
|
{
|
||||||
|
return UpdateEventStatus(eventId, EventStateEnum.InProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task MarkEventAsFailedAsync(Guid eventId)
|
||||||
|
{
|
||||||
|
return UpdateEventStatus(eventId, EventStateEnum.PublishedFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UpdateEventStatus(Guid eventId, EventStateEnum status)
|
||||||
|
{
|
||||||
|
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == eventId);
|
||||||
|
eventLogEntry.State = status;
|
||||||
|
|
||||||
|
if(status == EventStateEnum.InProgress)
|
||||||
eventLogEntry.TimesSent++;
|
eventLogEntry.TimesSent++;
|
||||||
eventLogEntry.State = EventStateEnum.Published;
|
|
||||||
|
|
||||||
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);
|
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);
|
||||||
|
|
||||||
|
@ -29,9 +29,16 @@ namespace Catalog.API.IntegrationEvents
|
|||||||
|
|
||||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
|
||||||
_eventBus.Publish(evt);
|
_eventBus.Publish(evt);
|
||||||
|
await _eventLogService.MarkEventAsPublishedAsync(evt.Id);
|
||||||
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
|
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
using IdentityModel;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IdentityModel;
|
||||||
using IdentityServer4;
|
using IdentityServer4;
|
||||||
using IdentityServer4.Models;
|
using IdentityServer4.Models;
|
||||||
using IdentityServer4.Services;
|
using IdentityServer4.Services;
|
||||||
@ -11,11 +16,6 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Models;
|
|||||||
using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels;
|
using Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels;
|
||||||
using Microsoft.eShopOnContainers.Services.Identity.API.Services;
|
using Microsoft.eShopOnContainers.Services.Identity.API.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
||||||
{
|
{
|
||||||
@ -79,9 +79,16 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
|||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
var user = await _loginService.FindByUsername(model.Email);
|
var user = await _loginService.FindByUsername(model.Email);
|
||||||
|
|
||||||
if (await _loginService.ValidateCredentials(user, model.Password))
|
if (await _loginService.ValidateCredentials(user, model.Password))
|
||||||
{
|
{
|
||||||
AuthenticationProperties props = null;
|
var props = new AuthenticationProperties
|
||||||
|
{
|
||||||
|
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2),
|
||||||
|
AllowRefresh = true,
|
||||||
|
RedirectUri = model.ReturnUrl
|
||||||
|
};
|
||||||
|
|
||||||
if (model.RememberMe)
|
if (model.RememberMe)
|
||||||
{
|
{
|
||||||
props = new AuthenticationProperties
|
props = new AuthenticationProperties
|
||||||
@ -91,7 +98,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
await _loginService.SignIn(user);
|
await _loginService.SignInAsync(user, props);
|
||||||
|
|
||||||
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
|
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
|
||||||
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
|
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
|
||||||
@ -113,7 +120,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
|||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
|
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
|
||||||
{
|
{
|
||||||
var allowLocal = true;
|
var allowLocal = true;
|
||||||
if (context?.ClientId != null)
|
if (context?.ClientId != null)
|
||||||
@ -132,7 +139,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<LoginViewModel> BuildLoginViewModelAsync(LoginViewModel model)
|
private async Task<LoginViewModel> BuildLoginViewModelAsync(LoginViewModel model)
|
||||||
{
|
{
|
||||||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
||||||
var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context);
|
var vm = await BuildLoginViewModelAsync(model.ReturnUrl, context);
|
||||||
@ -209,6 +216,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
|||||||
// delete authentication cookie
|
// delete authentication cookie
|
||||||
await HttpContext.SignOutAsync();
|
await HttpContext.SignOutAsync();
|
||||||
|
|
||||||
|
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
|
||||||
|
|
||||||
// set this so UI rendering sees an anonymous user
|
// set this so UI rendering sees an anonymous user
|
||||||
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||||
|
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.eShopOnContainers.Services.Identity.API.Models;
|
using Microsoft.eShopOnContainers.Services.Identity.API.Models;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
||||||
{
|
{
|
||||||
public class EFLoginService : ILoginService<ApplicationUser>
|
public class EFLoginService : ILoginService<ApplicationUser>
|
||||||
{
|
{
|
||||||
UserManager<ApplicationUser> _userManager;
|
private UserManager<ApplicationUser> _userManager;
|
||||||
SignInManager<ApplicationUser> _signInManager;
|
private SignInManager<ApplicationUser> _signInManager;
|
||||||
|
|
||||||
public EFLoginService(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) {
|
public EFLoginService(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
|
||||||
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
}
|
}
|
||||||
@ -24,8 +26,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
|||||||
return await _userManager.CheckPasswordAsync(user, password);
|
return await _userManager.CheckPasswordAsync(user, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SignIn(ApplicationUser user) {
|
public Task SignIn(ApplicationUser user)
|
||||||
|
{
|
||||||
return _signInManager.SignInAsync(user, true);
|
return _signInManager.SignInAsync(user, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SignInAsync(ApplicationUser user, AuthenticationProperties properties, string authenticationMethod = null)
|
||||||
|
{
|
||||||
|
return _signInManager.SignInAsync(user, properties, authenticationMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
||||||
{
|
{
|
||||||
public interface ILoginService<T>
|
public interface ILoginService<T>
|
||||||
{
|
{
|
||||||
Task<bool> ValidateCredentials(T user, string password);
|
Task<bool> ValidateCredentials(T user, string password);
|
||||||
|
|
||||||
Task<T> FindByUsername(string user);
|
Task<T> FindByUsername(string user);
|
||||||
|
|
||||||
Task SignIn(T user);
|
Task SignIn(T user);
|
||||||
|
|
||||||
|
Task SignInAsync(T user, AuthenticationProperties properties, string authenticationMethod = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ordering.API.Application.IntegrationEvents;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Behaviors
|
||||||
|
{
|
||||||
|
public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||||
|
{
|
||||||
|
private readonly ILogger<TransactionBehaviour<TRequest, TResponse>> _logger;
|
||||||
|
private readonly OrderingContext _dbContext;
|
||||||
|
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||||
|
|
||||||
|
public TransactionBehaviour(OrderingContext dbContext,
|
||||||
|
IOrderingIntegrationEventService orderingIntegrationEventService,
|
||||||
|
ILogger<TransactionBehaviour<TRequest, TResponse>> logger)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext));
|
||||||
|
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService));
|
||||||
|
_logger = logger ?? throw new ArgumentException(nameof(ILogger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
|
||||||
|
{
|
||||||
|
TResponse response = default(TResponse);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var strategy = _dbContext.Database.CreateExecutionStrategy();
|
||||||
|
await strategy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Begin transaction {typeof(TRequest).Name}");
|
||||||
|
|
||||||
|
await _dbContext.BeginTransactionAsync();
|
||||||
|
|
||||||
|
response = await next();
|
||||||
|
|
||||||
|
await _dbContext.CommitTransactionAsync();
|
||||||
|
|
||||||
|
_logger.LogInformation($"Committed transaction {typeof(TRequest).Name}");
|
||||||
|
|
||||||
|
await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}");
|
||||||
|
|
||||||
|
_dbContext.RollbackTransaction();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
|
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
|
||||||
{
|
{
|
||||||
using Domain.AggregatesModel.OrderAggregate;
|
using Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using global::Ordering.API.Application.IntegrationEvents;
|
||||||
|
using global::Ordering.API.Application.IntegrationEvents.Events;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
@ -15,17 +17,26 @@
|
|||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IOrderRepository _orderRepository;
|
||||||
private readonly IIdentityService _identityService;
|
private readonly IIdentityService _identityService;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||||
|
|
||||||
// Using DI to inject infrastructure persistence Repositories
|
// Using DI to inject infrastructure persistence Repositories
|
||||||
public CreateOrderCommandHandler(IMediator mediator, IOrderRepository orderRepository, IIdentityService identityService)
|
public CreateOrderCommandHandler(IMediator mediator,
|
||||||
|
IOrderingIntegrationEventService orderingIntegrationEventService,
|
||||||
|
IOrderRepository orderRepository,
|
||||||
|
IIdentityService identityService)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
|
||||||
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
|
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
|
||||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
|
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> Handle(CreateOrderCommand message, CancellationToken cancellationToken)
|
public async Task<bool> Handle(CreateOrderCommand message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// Add Integration event to clean the basket
|
||||||
|
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId);
|
||||||
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent);
|
||||||
|
|
||||||
// Add/Update the Buyer AggregateRoot
|
// Add/Update the Buyer AggregateRoot
|
||||||
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
|
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
|
||||||
// methods and constructor so validations, invariants and business logic
|
// methods and constructor so validations, invariants and business logic
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
public class SetAwaitingValidationOrderStatusCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int OrderNumber { get; private set; }
|
||||||
|
|
||||||
|
public SetAwaitingValidationOrderStatusCommand(int orderNumber)
|
||||||
|
{
|
||||||
|
OrderNumber = orderNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
// Regular CommandHandler
|
||||||
|
public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler<SetAwaitingValidationOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
|
||||||
|
public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler which processes the command when
|
||||||
|
/// graceperiod has finished
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
|
||||||
|
if(orderToUpdate == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
orderToUpdate.SetAwaitingValidationStatus();
|
||||||
|
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use for Idempotency in Command process
|
||||||
|
public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CreateResultForDuplicateRequest()
|
||||||
|
{
|
||||||
|
return true; // Ignore duplicate requests for processing order.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
public class SetPaidOrderStatusCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int OrderNumber { get; private set; }
|
||||||
|
|
||||||
|
public SetPaidOrderStatusCommand(int orderNumber)
|
||||||
|
{
|
||||||
|
OrderNumber = orderNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
// Regular CommandHandler
|
||||||
|
public class SetPaidOrderStatusCommandHandler : IRequestHandler<SetPaidOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
|
||||||
|
public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler which processes the command when
|
||||||
|
/// Shipment service confirms the payment
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Simulate a work time for validating the payment
|
||||||
|
await Task.Delay(10000);
|
||||||
|
|
||||||
|
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
|
||||||
|
if(orderToUpdate == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
orderToUpdate.SetPaidStatus();
|
||||||
|
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use for Idempotency in Command process
|
||||||
|
public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CreateResultForDuplicateRequest()
|
||||||
|
{
|
||||||
|
return true; // Ignore duplicate requests for processing order.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
public class SetStockConfirmedOrderStatusCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int OrderNumber { get; private set; }
|
||||||
|
|
||||||
|
public SetStockConfirmedOrderStatusCommand(int orderNumber)
|
||||||
|
{
|
||||||
|
OrderNumber = orderNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
// Regular CommandHandler
|
||||||
|
public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler<SetStockConfirmedOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
|
||||||
|
public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler which processes the command when
|
||||||
|
/// Stock service confirms the request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Simulate a work time for confirming the stock
|
||||||
|
await Task.Delay(10000);
|
||||||
|
|
||||||
|
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
|
||||||
|
if(orderToUpdate == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
orderToUpdate.SetStockConfirmedStatus();
|
||||||
|
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use for Idempotency in Command process
|
||||||
|
public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CreateResultForDuplicateRequest()
|
||||||
|
{
|
||||||
|
return true; // Ignore duplicate requests for processing order.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
public class SetStockRejectedOrderStatusCommand : IRequest<bool>
|
||||||
|
{
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int OrderNumber { get; private set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public List<int> OrderStockItems { get; private set; }
|
||||||
|
|
||||||
|
public SetStockRejectedOrderStatusCommand(int orderNumber, List<int> orderStockItems)
|
||||||
|
{
|
||||||
|
OrderNumber = orderNumber;
|
||||||
|
OrderStockItems = orderStockItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ordering.API.Application.Commands
|
||||||
|
{
|
||||||
|
// Regular CommandHandler
|
||||||
|
public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler<SetStockRejectedOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
|
||||||
|
public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler which processes the command when
|
||||||
|
/// Stock service rejects the request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Simulate a work time for rejecting the stock
|
||||||
|
await Task.Delay(10000);
|
||||||
|
|
||||||
|
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
|
||||||
|
if(orderToUpdate == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems);
|
||||||
|
|
||||||
|
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Use for Idempotency in Command process
|
||||||
|
public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>
|
||||||
|
{
|
||||||
|
public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool CreateResultForDuplicateRequest()
|
||||||
|
{
|
||||||
|
return true; // Ignore duplicate requests for processing order.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled
|
|||||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||||
|
|
||||||
var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToCancelledIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(
|
var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(
|
||||||
order.Id, order.OrderStatus.Name, buyer.Name, orderStockList);
|
order.Id, order.OrderStatus.Name, buyer.Name, orderStockList);
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
buyer.Name,
|
buyer.Name,
|
||||||
orderStockList);
|
orderStockList);
|
||||||
|
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped
|
|||||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||||
|
|
||||||
var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToShippedIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
|
|||||||
.SaveEntitiesAsync();
|
.SaveEntitiesAsync();
|
||||||
|
|
||||||
var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name);
|
var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name);
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedTosubmittedIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent);
|
||||||
|
|
||||||
_logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}.");
|
_logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}.");
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||||
|
|
||||||
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
|
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using MediatR;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Ordering.API.Application.Commands;
|
||||||
using Ordering.API.Application.IntegrationEvents.Events;
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -7,11 +9,11 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
|||||||
{
|
{
|
||||||
public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>
|
public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public GracePeriodConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
|
public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -24,9 +26,8 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
|
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId);
|
||||||
orderToUpdate.SetAwaitingValidationStatus();
|
await _mediator.Send(command);
|
||||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||||
{
|
{
|
||||||
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Ordering.API.Application.Commands;
|
||||||
using Ordering.API.Application.IntegrationEvents.Events;
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class OrderPaymentFailedIntegrationEventHandler :
|
public class OrderPaymentFailedIntegrationEventHandler :
|
||||||
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
|
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository)
|
public OrderPaymentFailedIntegrationEventHandler(IMediator mediator)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
|
public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
var command = new CancelOrderCommand(@event.OrderId);
|
||||||
|
await _mediator.Send(command);
|
||||||
orderToUpdate.SetCancelledStatus();
|
|
||||||
|
|
||||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,27 @@
|
|||||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||||
{
|
{
|
||||||
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using Ordering.API.Application.Commands;
|
||||||
using Ordering.API.Application.IntegrationEvents.Events;
|
using Ordering.API.Application.IntegrationEvents.Events;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
public class OrderPaymentSuccededIntegrationEventHandler :
|
public class OrderPaymentSuccededIntegrationEventHandler :
|
||||||
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
|
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository)
|
public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
|
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
// Simulate a work time for validating the payment
|
var command = new SetPaidOrderStatusCommand(@event.OrderId);
|
||||||
await Task.Delay(10000);
|
await _mediator.Send(command);
|
||||||
|
|
||||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
|
||||||
|
|
||||||
orderToUpdate.SetPaidStatus();
|
|
||||||
|
|
||||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,27 +4,24 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Events;
|
using Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using MediatR;
|
||||||
|
using System;
|
||||||
|
using Ordering.API.Application.Commands;
|
||||||
|
|
||||||
public class OrderStockConfirmedIntegrationEventHandler :
|
public class OrderStockConfirmedIntegrationEventHandler :
|
||||||
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
|
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
|
public OrderStockConfirmedIntegrationEventHandler(IMediator mediator)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
|
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
// Simulate a work time for confirming the stock
|
var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId);
|
||||||
await Task.Delay(10000);
|
await _mediator.Send(command);
|
||||||
|
|
||||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
|
||||||
|
|
||||||
orderToUpdate.SetStockConfirmedStatus();
|
|
||||||
|
|
||||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,27 +5,27 @@
|
|||||||
using Events;
|
using Events;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
|
using MediatR;
|
||||||
|
using Ordering.API.Application.Commands;
|
||||||
|
|
||||||
public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>
|
public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>
|
||||||
{
|
{
|
||||||
private readonly IOrderRepository _orderRepository;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public OrderStockRejectedIntegrationEventHandler(IOrderRepository orderRepository)
|
public OrderStockRejectedIntegrationEventHandler(IMediator mediator)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(OrderStockRejectedIntegrationEvent @event)
|
public async Task Handle(OrderStockRejectedIntegrationEvent @event)
|
||||||
{
|
{
|
||||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
|
||||||
|
|
||||||
var orderStockRejectedItems = @event.OrderStockItems
|
var orderStockRejectedItems = @event.OrderStockItems
|
||||||
.FindAll(c => !c.HasStock)
|
.FindAll(c => !c.HasStock)
|
||||||
.Select(c => c.ProductId);
|
.Select(c => c.ProductId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems);
|
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
|
||||||
|
await _mediator.Send(command);
|
||||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,14 +12,12 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
|||||||
{
|
{
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
private readonly ILoggerFactory _logger;
|
private readonly ILoggerFactory _logger;
|
||||||
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
|
||||||
|
|
||||||
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
|
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
|
||||||
ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService)
|
ILoggerFactory logger)
|
||||||
{
|
{
|
||||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -35,10 +33,6 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
|||||||
{
|
{
|
||||||
var result = false;
|
var result = false;
|
||||||
|
|
||||||
// Send Integration event to clean basket once basket is converted to Order and before starting with the order creation process
|
|
||||||
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(eventMsg.UserId);
|
|
||||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStartedIntegrationEvent);
|
|
||||||
|
|
||||||
if (eventMsg.RequestId != Guid.Empty)
|
if (eventMsg.RequestId != Guid.Empty)
|
||||||
{
|
{
|
||||||
var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street,
|
var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street,
|
||||||
|
@ -5,6 +5,7 @@ namespace Ordering.API.Application.IntegrationEvents
|
|||||||
{
|
{
|
||||||
public interface IOrderingIntegrationEventService
|
public interface IOrderingIntegrationEventService
|
||||||
{
|
{
|
||||||
Task PublishThroughEventBusAsync(IntegrationEvent evt);
|
Task PublishEventsThroughEventBusAsync();
|
||||||
|
Task AddAndSaveEventAsync(IntegrationEvent evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||||
using System;
|
using System;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ordering.API.Application.IntegrationEvents
|
namespace Ordering.API.Application.IntegrationEvents
|
||||||
@ -17,34 +19,42 @@ namespace Ordering.API.Application.IntegrationEvents
|
|||||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||||
private readonly IEventBus _eventBus;
|
private readonly IEventBus _eventBus;
|
||||||
private readonly OrderingContext _orderingContext;
|
private readonly OrderingContext _orderingContext;
|
||||||
|
private readonly IntegrationEventLogContext _eventLogContext;
|
||||||
private readonly IIntegrationEventLogService _eventLogService;
|
private readonly IIntegrationEventLogService _eventLogService;
|
||||||
|
|
||||||
public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext,
|
public OrderingIntegrationEventService(IEventBus eventBus,
|
||||||
|
OrderingContext orderingContext,
|
||||||
|
IntegrationEventLogContext eventLogContext,
|
||||||
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||||
{
|
{
|
||||||
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
|
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
|
||||||
|
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
|
||||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
|
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
|
||||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
|
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
public async Task PublishEventsThroughEventBusAsync()
|
||||||
{
|
{
|
||||||
await SaveEventAndOrderingContextChangesAsync(evt);
|
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
|
||||||
_eventBus.Publish(evt);
|
foreach (var logEvt in pendindLogEvents)
|
||||||
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId);
|
||||||
|
_eventBus.Publish(logEvt.IntegrationEvent);
|
||||||
|
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
|
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
|
||||||
{
|
{
|
||||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
|
||||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
|
||||||
await ResilientTransaction.New(_orderingContext)
|
|
||||||
.ExecuteAsync(async () => {
|
|
||||||
// Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction
|
|
||||||
await _orderingContext.SaveChangesAsync();
|
|
||||||
await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Autofac;
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
|
using Ordering.API.Application.Behaviors;
|
||||||
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
|
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
|
||||||
using Ordering.API.Application.Validations;
|
using Ordering.API.Application.Validations;
|
||||||
using Ordering.API.Infrastructure.Behaviors;
|
using Ordering.API.Infrastructure.Behaviors;
|
||||||
@ -40,6 +41,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
|
|||||||
|
|
||||||
builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
|
builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
|
||||||
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
|
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
|
||||||
|
builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||||
using Ordering.Infrastructure;
|
using Ordering.Infrastructure;
|
||||||
using Ordering.Infrastructure.EntityConfigurations;
|
using Ordering.Infrastructure.EntityConfigurations;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Data;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -23,9 +25,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
public DbSet<OrderStatus> OrderStatus { get; set; }
|
public DbSet<OrderStatus> OrderStatus { get; set; }
|
||||||
|
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private IDbContextTransaction _currentTransaction;
|
||||||
|
|
||||||
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
|
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
|
||||||
|
|
||||||
|
public IDbContextTransaction GetCurrentTransaction => _currentTransaction;
|
||||||
|
|
||||||
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
|
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
|
||||||
{
|
{
|
||||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||||
@ -61,6 +66,49 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task BeginTransactionAsync()
|
||||||
|
{
|
||||||
|
_currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CommitTransactionAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SaveChangesAsync();
|
||||||
|
_currentTransaction?.Commit();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
RollbackTransaction();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_currentTransaction != null)
|
||||||
|
{
|
||||||
|
_currentTransaction.Dispose();
|
||||||
|
_currentTransaction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RollbackTransaction()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_currentTransaction?.Rollback();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_currentTransaction != null)
|
||||||
|
{
|
||||||
|
_currentTransaction.Dispose();
|
||||||
|
_currentTransaction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrderingContextDesignFactory : IDesignTimeDbContextFactory<OrderingContext>
|
public class OrderingContextDesignFactory : IDesignTimeDbContextFactory<OrderingContext>
|
||||||
|
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace UnitTest.Ordering.Application
|
namespace UnitTest.Ordering.Application
|
||||||
{
|
{
|
||||||
|
using global::Ordering.API.Application.IntegrationEvents;
|
||||||
using global::Ordering.API.Application.Models;
|
using global::Ordering.API.Application.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
@ -22,12 +23,14 @@ namespace UnitTest.Ordering.Application
|
|||||||
private readonly Mock<IOrderRepository> _orderRepositoryMock;
|
private readonly Mock<IOrderRepository> _orderRepositoryMock;
|
||||||
private readonly Mock<IIdentityService> _identityServiceMock;
|
private readonly Mock<IIdentityService> _identityServiceMock;
|
||||||
private readonly Mock<IMediator> _mediator;
|
private readonly Mock<IMediator> _mediator;
|
||||||
|
private readonly Mock<IOrderingIntegrationEventService> _orderingIntegrationEventService;
|
||||||
|
|
||||||
public NewOrderRequestHandlerTest()
|
public NewOrderRequestHandlerTest()
|
||||||
{
|
{
|
||||||
|
|
||||||
_orderRepositoryMock = new Mock<IOrderRepository>();
|
_orderRepositoryMock = new Mock<IOrderRepository>();
|
||||||
_identityServiceMock = new Mock<IIdentityService>();
|
_identityServiceMock = new Mock<IIdentityService>();
|
||||||
|
_orderingIntegrationEventService = new Mock<IOrderingIntegrationEventService>();
|
||||||
_mediator = new Mock<IMediator>();
|
_mediator = new Mock<IMediator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +51,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
|
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
|
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
|
||||||
var cltToken = new System.Threading.CancellationToken();
|
var cltToken = new System.Threading.CancellationToken();
|
||||||
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user