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\azure\service.yaml
|
||||
kubectl apply -f nginx-ingress\patch-service-without-rbac.yaml
|
||||
kubectl apply -f nginx-ingress\cloud-generic.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
|
||||
kubectl apply -f nginx-ingress\namespace.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
|
||||
kubectl apply -f nginx-ingress\mandatory.yaml
|
||||
|
||||
|
||||
|
||||
|
@ -113,6 +113,7 @@ ExecKube -cmd 'delete configmap internalurls'
|
||||
ExecKube -cmd 'delete configmap urls'
|
||||
ExecKube -cmd 'delete configmap externalcfg'
|
||||
ExecKube -cmd 'delete configmap ocelot'
|
||||
ExecKube -cmd 'delete -f ingress.yaml'
|
||||
|
||||
# start sql, rabbitmq, frontend deployments
|
||||
if ($deployInfrastructure) {
|
||||
@ -204,5 +205,8 @@ ExecKube -cmd 'rollout resume deployments/apigwwm'
|
||||
ExecKube -cmd 'rollout resume deployments/apigwws'
|
||||
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
|
||||
|
||||
|
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)][string]$aksName="",
|
||||
[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
|
||||
|
||||
$ingressValuesFile="ingress_values.yaml"
|
||||
|
||||
if ($ingressValuesFile) {
|
||||
$ingressValuesFile="ingress_values_dockerk8s.yaml"
|
||||
$dns="localhost"
|
||||
}
|
||||
|
||||
if ($externalDns -eq "aks") {
|
||||
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
|
||||
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) {
|
||||
foreach ($infra in $infras) {
|
||||
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) {
|
||||
Write-Host "Installing: $chart" -ForegroundColor Green
|
||||
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 {
|
||||
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>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Ocelot" Version="3.0.0" />
|
||||
<PackageReference Include="Ocelot" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,11 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CacheManager.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
|
||||
{
|
||||
@ -10,7 +11,17 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events
|
||||
CreationDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
public DateTime CreationDate { get; }
|
||||
[JsonConstructor]
|
||||
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 Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
{
|
||||
@ -37,8 +35,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus
|
||||
where TH : IIntegrationEventHandler<T>
|
||||
{
|
||||
var eventName = GetEventKey<T>();
|
||||
|
||||
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
|
||||
_eventTypes.Add(typeof(T));
|
||||
|
||||
if (!_eventTypes.Contains(typeof(T)))
|
||||
{
|
||||
_eventTypes.Add(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
|
||||
|
@ -217,14 +217,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||
if (handler == null) continue;
|
||||
dynamic eventData = JObject.Parse(message);
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
|
@ -163,14 +163,16 @@
|
||||
if (subscription.IsDynamic)
|
||||
{
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
|
||||
if (handler == null) continue;
|
||||
dynamic eventData = JObject.Parse(message);
|
||||
await handler.Handle(eventData);
|
||||
}
|
||||
else
|
||||
{
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||
var handler = scope.ResolveOptional(subscription.HandlerType);
|
||||
if (handler == null) continue;
|
||||
var eventType = _subsManager.GetEventTypeByName(eventName);
|
||||
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
|
||||
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
|
||||
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
||||
public enum EventStateEnum
|
||||
{
|
||||
NotPublished = 0,
|
||||
Published = 1,
|
||||
PublishedFailed = 2
|
||||
InProgress = 1,
|
||||
Published = 2,
|
||||
PublishedFailed = 3
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using System.Linq;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
||||
{
|
||||
@ -11,7 +14,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
||||
private IntegrationEventLogEntry() { }
|
||||
public IntegrationEventLogEntry(IntegrationEvent @event)
|
||||
{
|
||||
EventId = @event.Id;
|
||||
EventId = @event.Id;
|
||||
CreationTime = @event.CreationDate;
|
||||
EventTypeName = @event.GetType().FullName;
|
||||
Content = JsonConvert.SerializeObject(@event);
|
||||
@ -20,9 +23,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
|
||||
}
|
||||
public Guid EventId { 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 int TimesSent { get; set; }
|
||||
public DateTime CreationTime { 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
|
||||
{
|
||||
Task<IEnumerable<IntegrationEventLogEntry>> RetrieveEventLogsPendingToPublishAsync();
|
||||
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.Diagnostics;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
|
||||
@ -12,6 +17,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
||||
{
|
||||
private readonly IntegrationEventLogContext _integrationEventLogContext;
|
||||
private readonly DbConnection _dbConnection;
|
||||
private readonly List<Type> _eventTypes;
|
||||
|
||||
public IntegrationEventLogService(DbConnection dbConnection)
|
||||
{
|
||||
@ -21,6 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
||||
.UseSqlServer(_dbConnection)
|
||||
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
|
||||
.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)
|
||||
@ -38,11 +58,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
|
||||
return _integrationEventLogContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public Task MarkEventAsPublishedAsync(IntegrationEvent @event)
|
||||
public Task MarkEventAsPublishedAsync(Guid eventId)
|
||||
{
|
||||
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id);
|
||||
eventLogEntry.TimesSent++;
|
||||
eventLogEntry.State = EventStateEnum.Published;
|
||||
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++;
|
||||
|
||||
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);
|
||||
|
||||
|
@ -29,9 +29,16 @@ namespace Catalog.API.IntegrationEvents
|
||||
|
||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
||||
{
|
||||
_eventBus.Publish(evt);
|
||||
|
||||
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
||||
try
|
||||
{
|
||||
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
|
||||
_eventBus.Publish(evt);
|
||||
await _eventLogService.MarkEventAsPublishedAsync(evt.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)
|
||||
|
@ -232,7 +232,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
|
||||
public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||
|
||||
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();
|
||||
|
||||
|
@ -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.Models;
|
||||
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.Services;
|
||||
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
|
||||
{
|
||||
@ -79,9 +79,16 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var user = await _loginService.FindByUsername(model.Email);
|
||||
|
||||
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)
|
||||
{
|
||||
props = new AuthenticationProperties
|
||||
@ -91,8 +98,8 @@ 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
|
||||
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
|
||||
{
|
||||
@ -113,7 +120,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
|
||||
private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, AuthorizationRequest context)
|
||||
{
|
||||
var allowLocal = true;
|
||||
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 vm = await BuildLoginViewModelAsync(model.ReturnUrl, context);
|
||||
@ -193,7 +200,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
// hack: try/catch to handle social providers that throw
|
||||
await HttpContext.SignOutAsync(idp, new AuthenticationProperties
|
||||
{
|
||||
@ -209,6 +216,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
|
||||
// delete authentication cookie
|
||||
await HttpContext.SignOutAsync();
|
||||
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
|
||||
|
||||
// set this so UI rendering sees an anonymous user
|
||||
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 System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
||||
{
|
||||
public class EFLoginService : ILoginService<ApplicationUser>
|
||||
{
|
||||
UserManager<ApplicationUser> _userManager;
|
||||
SignInManager<ApplicationUser> _signInManager;
|
||||
private UserManager<ApplicationUser> _userManager;
|
||||
private SignInManager<ApplicationUser> _signInManager;
|
||||
|
||||
public EFLoginService(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) {
|
||||
public EFLoginService(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
@ -24,8 +26,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
||||
return await _userManager.CheckPasswordAsync(user, password);
|
||||
}
|
||||
|
||||
public Task SignIn(ApplicationUser user) {
|
||||
public Task SignIn(ApplicationUser user)
|
||||
{
|
||||
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 Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace Microsoft.eShopOnContainers.Services.Identity.API.Services
|
||||
{
|
||||
public interface ILoginService<T>
|
||||
{
|
||||
Task<bool> ValidateCredentials(T user, string password);
|
||||
|
||||
Task<T> FindByUsername(string 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
|
||||
{
|
||||
using Domain.AggregatesModel.OrderAggregate;
|
||||
using global::Ordering.API.Application.IntegrationEvents;
|
||||
using global::Ordering.API.Application.IntegrationEvents.Events;
|
||||
using MediatR;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||
@ -15,17 +17,26 @@
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IIdentityService _identityService;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||
|
||||
// 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));
|
||||
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
||||
}
|
||||
|
||||
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
|
||||
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
|
||||
// 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 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(
|
||||
order.Id, order.OrderStatus.Name, buyer.Name, orderStockList);
|
||||
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
|
||||
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
|
||||
}
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@
|
||||
buyer.Name,
|
||||
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 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();
|
||||
|
||||
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}.");
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
||||
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
|
||||
|
||||
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 Ordering.API.Application.Commands;
|
||||
using Ordering.API.Application.IntegrationEvents.Events;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -7,11 +9,11 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
{
|
||||
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>
|
||||
@ -24,9 +26,8 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
/// <returns></returns>
|
||||
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
|
||||
{
|
||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
||||
orderToUpdate.SetAwaitingValidationStatus();
|
||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId);
|
||||
await _mediator.Send(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
{
|
||||
using MediatR;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using Ordering.API.Application.Commands;
|
||||
using Ordering.API.Application.IntegrationEvents.Events;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class OrderPaymentFailedIntegrationEventHandler :
|
||||
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)
|
||||
{
|
||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
||||
|
||||
orderToUpdate.SetCancelledStatus();
|
||||
|
||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||
var command = new CancelOrderCommand(@event.OrderId);
|
||||
await _mediator.Send(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
{
|
||||
using MediatR;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using Ordering.API.Application.Commands;
|
||||
using Ordering.API.Application.IntegrationEvents.Events;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class OrderPaymentSuccededIntegrationEventHandler :
|
||||
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)
|
||||
{
|
||||
// Simulate a work time for validating the payment
|
||||
await Task.Delay(10000);
|
||||
|
||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
||||
|
||||
orderToUpdate.SetPaidStatus();
|
||||
|
||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||
var command = new SetPaidOrderStatusCommand(@event.OrderId);
|
||||
await _mediator.Send(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,27 +4,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Events;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using MediatR;
|
||||
using System;
|
||||
using Ordering.API.Application.Commands;
|
||||
|
||||
public class OrderStockConfirmedIntegrationEventHandler :
|
||||
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)
|
||||
{
|
||||
// Simulate a work time for confirming the stock
|
||||
await Task.Delay(10000);
|
||||
|
||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
||||
|
||||
orderToUpdate.SetStockConfirmedStatus();
|
||||
|
||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||
var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId);
|
||||
await _mediator.Send(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,27 +5,27 @@
|
||||
using Events;
|
||||
using System.Linq;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using MediatR;
|
||||
using Ordering.API.Application.Commands;
|
||||
|
||||
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)
|
||||
{
|
||||
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
|
||||
|
||||
var orderStockRejectedItems = @event.OrderStockItems
|
||||
.FindAll(c => !c.HasStock)
|
||||
.Select(c => c.ProductId);
|
||||
.Select(c => c.ProductId)
|
||||
.ToList();
|
||||
|
||||
orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems);
|
||||
|
||||
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
|
||||
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
|
||||
await _mediator.Send(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,15 +11,13 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILoggerFactory _logger;
|
||||
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
|
||||
private readonly ILoggerFactory _logger;
|
||||
|
||||
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
|
||||
ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService)
|
||||
ILoggerFactory logger)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -34,11 +32,7 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
|
||||
public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
Task PublishThroughEventBusAsync(IntegrationEvent evt);
|
||||
Task PublishEventsThroughEventBusAsync();
|
||||
Task AddAndSaveEventAsync(IntegrationEvent evt);
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,14 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ordering.API.Application.IntegrationEvents
|
||||
@ -17,34 +19,42 @@ namespace Ordering.API.Application.IntegrationEvents
|
||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly OrderingContext _orderingContext;
|
||||
private readonly IntegrationEventLogContext _eventLogContext;
|
||||
private readonly IIntegrationEventLogService _eventLogService;
|
||||
|
||||
public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext,
|
||||
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||
public OrderingIntegrationEventService(IEventBus eventBus,
|
||||
OrderingContext orderingContext,
|
||||
IntegrationEventLogContext eventLogContext,
|
||||
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||
{
|
||||
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
|
||||
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
|
||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
|
||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
|
||||
}
|
||||
|
||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
|
||||
public async Task PublishEventsThroughEventBusAsync()
|
||||
{
|
||||
await SaveEventAndOrderingContextChangesAsync(evt);
|
||||
_eventBus.Publish(evt);
|
||||
await _eventLogService.MarkEventAsPublishedAsync(evt);
|
||||
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
|
||||
foreach (var logEvt in pendindLogEvents)
|
||||
{
|
||||
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():
|
||||
//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());
|
||||
});
|
||||
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Autofac;
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||
using Ordering.API.Application.Behaviors;
|
||||
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
|
||||
using Ordering.API.Application.Validations;
|
||||
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(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
|
||||
builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@
|
||||
eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>>();
|
||||
eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>();
|
||||
eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>();
|
||||
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
|
||||
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
|
||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||
using Ordering.Infrastructure;
|
||||
using Ordering.Infrastructure.EntityConfigurations;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -23,9 +25,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
||||
public DbSet<OrderStatus> OrderStatus { get; set; }
|
||||
|
||||
private readonly IMediator _mediator;
|
||||
private IDbContextTransaction _currentTransaction;
|
||||
|
||||
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
|
||||
|
||||
public IDbContextTransaction GetCurrentTransaction => _currentTransaction;
|
||||
|
||||
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
|
||||
{
|
||||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
|
||||
@ -60,7 +65,50 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
|
||||
var result = await base.SaveChangesAsync();
|
||||
|
||||
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>
|
||||
|
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace UnitTest.Ordering.Application
|
||||
{
|
||||
using global::Ordering.API.Application.IntegrationEvents;
|
||||
using global::Ordering.API.Application.Models;
|
||||
using MediatR;
|
||||
using System.Collections;
|
||||
@ -22,12 +23,14 @@ namespace UnitTest.Ordering.Application
|
||||
private readonly Mock<IOrderRepository> _orderRepositoryMock;
|
||||
private readonly Mock<IIdentityService> _identityServiceMock;
|
||||
private readonly Mock<IMediator> _mediator;
|
||||
private readonly Mock<IOrderingIntegrationEventService> _orderingIntegrationEventService;
|
||||
|
||||
public NewOrderRequestHandlerTest()
|
||||
{
|
||||
|
||||
_orderRepositoryMock = new Mock<IOrderRepository>();
|
||||
_identityServiceMock = new Mock<IIdentityService>();
|
||||
_orderingIntegrationEventService = new Mock<IOrderingIntegrationEventService>();
|
||||
_mediator = new Mock<IMediator>();
|
||||
}
|
||||
|
||||
@ -48,7 +51,7 @@ namespace UnitTest.Ordering.Application
|
||||
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
|
||||
|
||||
//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 result = await handler.Handle(fakeOrderCmd, cltToken);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user