Browse Source

Merge pull request #43 from dotnet-architecture/dev

upd fork
pull/1934/head
Taras Kovalenko 6 years ago
committed by GitHub
parent
commit
6326194680
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1029 additions and 351 deletions
  1. +1
    -3
      k8s/deploy-ingress-azure.ps1
  2. +2
    -0
      k8s/deploy-ingress-dockerlocal.ps1
  3. +1
    -8
      k8s/deploy-ingress.ps1
  4. +4
    -0
      k8s/deploy.ps1
  5. +18
    -0
      k8s/helm-rbac.yaml
  6. +13
    -5
      k8s/helm/deploy-all.ps1
  7. +5
    -0
      k8s/helm/ingress_values_dockerk8s.yaml
  8. +0
    -19
      k8s/nginx-ingress/azure/service.yaml
  9. +21
    -0
      k8s/nginx-ingress/cloud-generic.yaml
  10. BIN
      k8s/nginx-ingress/cm.yaml
  11. +0
    -11
      k8s/nginx-ingress/configmap.yaml
  12. +0
    -52
      k8s/nginx-ingress/default-backend.yaml
  13. +3
    -0
      k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml
  14. +3
    -0
      k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml
  15. +39
    -0
      k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml
  16. +238
    -0
      k8s/nginx-ingress/mandatory.yaml
  17. +0
    -4
      k8s/nginx-ingress/namespace.yaml
  18. +0
    -40
      k8s/nginx-ingress/patch-service-without-rbac.yaml
  19. +0
    -7
      k8s/nginx-ingress/publish-service-patch.yaml
  20. +22
    -0
      k8s/nginx-ingress/service-nodeport.yaml
  21. +0
    -5
      k8s/nginx-ingress/tcp-services-configmap.yaml
  22. +0
    -5
      k8s/nginx-ingress/udp-services-configmap.yaml
  23. +0
    -61
      k8s/nginx-ingress/without-rbac.yaml
  24. +1
    -1
      src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj
  25. +1
    -7
      src/ApiGateways/ApiGw-Base/Startup.cs
  26. +13
    -2
      src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs
  27. +9
    -6
      src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs
  28. +3
    -1
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  29. +4
    -2
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs
  30. +3
    -2
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs
  31. +14
    -1
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs
  32. +4
    -1
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs
  33. +41
    -4
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs
  34. +10
    -3
      src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs
  35. +1
    -1
      src/Services/Catalog/Catalog.API/Startup.cs
  36. +21
    -12
      src/Services/Identity/Identity.API/Controllers/AccountController.cs
  37. +14
    -6
      src/Services/Identity/Identity.API/Services/EFLoginService.cs
  38. +5
    -0
      src/Services/Identity/Identity.API/Services/ILoginService.cs
  39. +62
    -0
      src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs
  40. +12
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  41. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs
  42. +52
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs
  43. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs
  44. +55
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs
  45. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs
  46. +55
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs
  47. +25
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs
  48. +56
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs
  49. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs
  50. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs
  51. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs
  52. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs
  53. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  54. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs
  55. +8
    -7
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs
  56. +8
    -8
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs
  57. +8
    -11
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs
  58. +8
    -11
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  59. +9
    -9
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs
  60. +4
    -10
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs
  61. +2
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs
  62. +25
    -15
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs
  63. +3
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  64. +1
    -1
      src/Services/Ordering/Ordering.API/Startup.cs
  65. +49
    -1
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  66. +4
    -1
      src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs

+ 1
- 3
k8s/deploy-ingress-azure.ps1 View File

@ -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
- 0
k8s/deploy-ingress-dockerlocal.ps1 View File

@ -0,0 +1,2 @@
kubectl apply -f nginx-ingress\cm.yaml
kubectl apply -f nginx-ingress\cloud-generic.yaml

+ 1
- 8
k8s/deploy-ingress.ps1 View File

@ -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

+ 4
- 0
k8s/deploy.ps1 View File

@ -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
- 0
k8s/helm-rbac.yaml View 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

+ 13
- 5
k8s/helm/deploy-all.ps1 View File

@ -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
- 0
k8s/helm/ingress_values_dockerk8s.yaml View 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"

+ 0
- 19
k8s/nginx-ingress/azure/service.yaml View File

@ -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
- 0
k8s/nginx-ingress/cloud-generic.yaml View 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 View File


+ 0
- 11
k8s/nginx-ingress/configmap.yaml View File

@ -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"

+ 0
- 52
k8s/nginx-ingress/default-backend.yaml View File

@ -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

+ 3
- 0
k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml View File

@ -0,0 +1,3 @@
data:
mvc_e: http://10.0.75.1/webmvc

+ 3
- 0
k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml View File

@ -0,0 +1,3 @@
data:
urls__IdentityUrl: http://10.0.75.1/identity
urls__mvc: http://10.0.75.1/webmvc

+ 39
- 0
k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml View 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
- 0
k8s/nginx-ingress/mandatory.yaml View 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

+ 0
- 4
k8s/nginx-ingress/namespace.yaml View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx

+ 0
- 40
k8s/nginx-ingress/patch-service-without-rbac.yaml View File

@ -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

+ 0
- 7
k8s/nginx-ingress/publish-service-patch.yaml View File

@ -1,7 +0,0 @@
[
{
'op': 'add',
'path': '/spec/template/spec/containers/0/args/-',
'value': '--publish-service=$(POD_NAMESPACE)/ingress-nginx'
}
]

+ 22
- 0
k8s/nginx-ingress/service-nodeport.yaml View 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

+ 0
- 5
k8s/nginx-ingress/tcp-services-configmap.yaml View File

@ -1,5 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx

+ 0
- 5
k8s/nginx-ingress/udp-services-configmap.yaml View File

@ -1,5 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx

+ 0
- 61
k8s/nginx-ingress/without-rbac.yaml View File

@ -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

+ 1
- 1
src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj View File

@ -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
- 7
src/ApiGateways/ApiGw-Base/Startup.cs View File

@ -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;


+ 13
- 2
src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs View File

@ -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; }
}
}

+ 9
- 6
src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs View File

@ -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)


+ 3
- 1
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -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 });
}


+ 4
- 2
src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs View File

@ -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 });
}


+ 3
- 2
src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs View File

@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
public enum EventStateEnum
{
NotPublished = 0,
Published = 1,
PublishedFailed = 2
InProgress = 1,
Published = 2,
PublishedFailed = 3
}
}

+ 14
- 1
src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs View File

@ -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;
}
}
}

+ 4
- 1
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs View File

@ -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);
}
}

+ 41
- 4
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs View File

@ -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)
{
return UpdateEventStatus(eventId, EventStateEnum.Published);
}
public Task MarkEventAsInProgressAsync(Guid eventId)
{
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id);
eventLogEntry.TimesSent++;
eventLogEntry.State = EventStateEnum.Published;
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);


+ 10
- 3
src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs View File

@ -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)


+ 1
- 1
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -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>();


+ 21
- 12
src/Services/Identity/Identity.API/Controllers/AccountController.cs View File

@ -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());


+ 14
- 6
src/Services/Identity/Identity.API/Services/EFLoginService.cs View File

@ -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);
}
}
}

+ 5
- 0
src/Services/Identity/Identity.API/Services/ILoginService.cs View File

@ -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);
}
}

+ 62
- 0
src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs View File

@ -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;
}
}
}
}

+ 12
- 1
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -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


+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs View File

@ -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;
}
}
}

+ 52
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs View File

@ -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.
}
}
}

+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs View File

@ -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;
}
}
}

+ 55
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs View File

@ -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.
}
}
}

+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs View File

@ -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;
}
}
}

+ 55
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs View File

@ -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.
}
}
}

+ 25
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs View File

@ -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;
}
}
}

+ 56
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs View File

@ -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.
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs View File

@ -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);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs View File

@ -46,7 +46,7 @@
var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(
order.Id, order.OrderStatus.Name, buyer.Name, orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs View File

@ -51,7 +51,7 @@
buyer.Name,
orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs View File

@ -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);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -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}.");
}


+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs View File

@ -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);
}
}
}

+ 8
- 7
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs View File

@ -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);
}
}
}

+ 8
- 8
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs View File

@ -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);
}
}
}

+ 8
- 11
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs View File

@ -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);
}
}
}

+ 8
- 11
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -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);
}
}
}

+ 9
- 9
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs View File

@ -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);
orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems);
.Select(c => c.ProductId)
.ToList();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
await _mediator.Send(command);
}
}
}

+ 4
- 10
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs View File

@ -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,


+ 2
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs View File

@ -5,6 +5,7 @@ namespace Ordering.API.Application.IntegrationEvents
{
public interface IOrderingIntegrationEventService
{
Task PublishThroughEventBusAsync(IntegrationEvent evt);
Task PublishEventsThroughEventBusAsync();
Task AddAndSaveEventAsync(IntegrationEvent evt);
}
}

+ 25
- 15
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -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());
}
}
}

+ 3
- 0
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -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<,>));
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -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>>();
}


+ 49
- 1
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -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>


+ 4
- 1
src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs View File

@ -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…
Cancel
Save