Repository: meysamhadeli/booking-microservices-sample
Branch: main
Commit: d99d1a9a9a9d
Files: 555
Total size: 1.5 MB
Directory structure:
gitextract_4va7s4qm/
├── .aspire/
│ └── settings.json
├── .config/
│ └── dotnet-tools.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── actions/
│ │ ├── build/
│ │ │ └── action.yml
│ │ ├── build-test/
│ │ │ └── action.yml
│ │ ├── docker-build-publish/
│ │ │ └── action.yml
│ │ └── test/
│ │ └── action.yml
│ ├── release-drafter.yml
│ └── workflows/
│ ├── ci.yml
│ └── release-drafter-labeler.yml
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── CONTRIBUTION.md
├── Directory.Build.props
├── LICENSE
├── README.md
├── assets/
│ ├── booking-microservices.drawio
│ └── vertical-slice-architecture.excalidraw
├── booking-microservices.sln
├── booking.rest
├── commitlint.config.js
├── deployments/
│ ├── configs/
│ │ ├── dashboards.md
│ │ ├── grafana/
│ │ │ ├── dashboards/
│ │ │ │ ├── dotnet-core-endpoint.json
│ │ │ │ ├── dotnet-core.json
│ │ │ │ ├── node-exporter.json
│ │ │ │ ├── postgresql.json
│ │ │ │ └── rabbitmq.json
│ │ │ └── provisioning/
│ │ │ ├── dashboards/
│ │ │ │ └── dashboard.yml
│ │ │ └── datasources/
│ │ │ └── datasource.yml
│ │ ├── loki-config.yaml
│ │ ├── otel-collector-config.yaml
│ │ ├── prometheus.yaml
│ │ └── tempo.yaml
│ ├── docker-compose/
│ │ ├── docker-compose.infrastructure.yaml
│ │ └── docker-compose.yaml
│ └── kubernetes/
│ ├── booking-cert-manager.yml
│ └── booking-microservices.yml
├── global.json
├── package.json
├── scripts/
│ └── setup_kubectl_gitpod.sh
└── src/
├── ApiGateway/
│ ├── Dockerfile
│ └── src/
│ ├── ApiGateway.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── appsettings.Development.json
│ ├── appsettings.docker.json
│ └── appsettings.json
├── Aspire/
│ └── src/
│ ├── AppHost/
│ │ ├── AppHost.csproj
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ └── appsettings.json
│ └── ServiceDefaults/
│ ├── Extensions.cs
│ └── ServiceDefaults.csproj
├── BuildingBlocks/
│ ├── BuildingBlocks.csproj
│ ├── Caching/
│ │ ├── CachingBehavior.cs
│ │ ├── ICacheRequest.cs
│ │ ├── IInvalidateCacheRequest.cs
│ │ └── InvalidateCachingBehavior.cs
│ ├── Constants/
│ │ └── IdentityConstant.cs
│ ├── Contracts/
│ │ └── EventBus.Messages/
│ │ ├── FlighContracts.cs
│ │ ├── IdentityContracts.cs
│ │ ├── PassengerContracts.cs
│ │ └── ReservationContracts.cs
│ ├── Core/
│ │ ├── CQRS/
│ │ │ ├── ICommand.cs
│ │ │ ├── ICommandHandler.cs
│ │ │ ├── IQuery.cs
│ │ │ └── IQueryHandler.cs
│ │ ├── CompositeEventMapper.cs
│ │ ├── Event/
│ │ │ ├── EventType.cs
│ │ │ ├── IDomainEvent.cs
│ │ │ ├── IEvent.cs
│ │ │ ├── IHaveIntegrationEvent.cs
│ │ │ ├── IIntegrationEvent.cs
│ │ │ ├── IInternalCommand.cs
│ │ │ ├── InternalCommand.cs
│ │ │ └── MessageEnvelope.cs
│ │ ├── EventDispatcher.cs
│ │ ├── IEventDispatcher.cs
│ │ ├── IEventMapper.cs
│ │ ├── IntegrationEventWrapper.cs
│ │ ├── Model/
│ │ │ ├── Aggregate.cs
│ │ │ ├── Entity.cs
│ │ │ ├── IAggregate.cs
│ │ │ ├── IEntity.cs
│ │ │ └── IVersion.cs
│ │ └── Pagination/
│ │ ├── Extensions.cs
│ │ ├── IPageList.cs
│ │ ├── IPageQuery.cs
│ │ ├── IPageRequest.cs
│ │ └── PageList.cs
│ ├── EFCore/
│ │ ├── AppDbContextBase.cs
│ │ ├── DesignTimeDbContextFactoryBase.cs
│ │ ├── EfTxBehavior.cs
│ │ ├── Extensions.cs
│ │ ├── IDataSeeder.cs
│ │ ├── IDbContext.cs
│ │ ├── ISeedManager.cs
│ │ ├── PostgresOptions.cs
│ │ └── SeedManagers.cs
│ ├── EventStoreDB/
│ │ ├── BackgroundWorkers/
│ │ │ └── BackgroundWorker.cs
│ │ ├── Config.cs
│ │ ├── Events/
│ │ │ ├── AggregateEventSourcing.cs
│ │ │ ├── AggregateStreamExtensions.cs
│ │ │ ├── EventTypeMapper.cs
│ │ │ ├── IAggregateEventSourcing.cs
│ │ │ ├── IEventHandler.cs
│ │ │ ├── IExternalEvent.cs
│ │ │ ├── IProjection.cs
│ │ │ ├── StreamEvent.cs
│ │ │ ├── StreamEventExtensions.cs
│ │ │ └── StreamNameMapper.cs
│ │ ├── Extensions.cs
│ │ ├── Projections/
│ │ │ ├── IProjectionProcessor.cs
│ │ │ ├── IProjectionPublisher.cs
│ │ │ └── ProjectionPublisher.cs
│ │ ├── Repository/
│ │ │ ├── EventStoreDBRepository.cs
│ │ │ └── RepositoryExtensions.cs
│ │ ├── Serialization/
│ │ │ ├── EventStoreDBSerializer.cs
│ │ │ ├── JsonObjectContractProvider.cs
│ │ │ ├── NonDefaultConstructorContractResolver.cs
│ │ │ └── SerializationExtensions.cs
│ │ └── Subscriptions/
│ │ ├── EventStoreDBSubscriptionCheckpointRepository.cs
│ │ ├── EventStoreDBSubscriptionToAll.cs
│ │ ├── ISubscriptionCheckpointRepository.cs
│ │ └── InMemorySubscriptionCheckpointRepository.cs
│ ├── Exception/
│ │ ├── AggregateNotFoundException.cs
│ │ ├── AppException.cs
│ │ ├── BadRequestException.cs
│ │ ├── ConflictException.cs
│ │ ├── CustomException.cs
│ │ ├── DomainException.cs
│ │ ├── GrpcExceptionInterceptor.cs
│ │ ├── InternalServerException.cs
│ │ ├── NotFoundException.cs
│ │ ├── ProblemDetailsWithCode.cs
│ │ └── ValidationException.cs
│ ├── HealthCheck/
│ │ ├── Extensions.cs
│ │ └── HealthOptions.cs
│ ├── Jwt/
│ │ ├── AuthHeaderHandler.cs
│ │ └── JwtExtensions.cs
│ ├── Logging/
│ │ └── LoggingBehavior.cs
│ ├── Mapster/
│ │ └── Extensions.cs
│ ├── MassTransit/
│ │ ├── ConsumeFilter.cs
│ │ ├── Extensions.cs
│ │ ├── RabbitMqOptions.cs
│ │ └── TransportType.cs
│ ├── Mongo/
│ │ ├── Extensions.cs
│ │ ├── IMongoDbContext.cs
│ │ ├── IMongoRepository.cs
│ │ ├── IMongoUnitOfWork.cs
│ │ ├── IRepository.cs
│ │ ├── ITransactionAble.cs
│ │ ├── IUnitOfWork.cs
│ │ ├── ImmutablePocoConvention.cs
│ │ ├── MicroBootstrap.Persistence.Mongo.csproj
│ │ ├── MongoDbContext.cs
│ │ ├── MongoOptions.cs
│ │ ├── MongoRepository.cs
│ │ └── MongoUnitOfWork.cs
│ ├── OpenApi/
│ │ ├── Extensions.cs
│ │ └── SecuritySchemeDocumentTransformer.cs
│ ├── OpenTelemetryCollector/
│ │ ├── ActivityExtensions.cs
│ │ ├── ActivityInfo.cs
│ │ ├── Behaviors/
│ │ │ └── ObservabilityPipelineBehavior.cs
│ │ ├── CoreDiagnostics/
│ │ │ ├── Commands/
│ │ │ │ ├── CommandHandlerActivity.cs
│ │ │ │ └── CommandHandlerMetrics.cs
│ │ │ └── Query/
│ │ │ ├── QueryHandlerActivity.cs
│ │ │ └── QueryHandlerMetrics.cs
│ │ ├── CreateActivityInfo.cs
│ │ ├── DiagnosticsProvider/
│ │ │ ├── CustomeDiagnosticsProvider.cs
│ │ │ └── IDiagnosticsProvider.cs
│ │ ├── Extensions.cs
│ │ ├── ObservabilityConstant.cs
│ │ ├── ObservabilityOptions.cs
│ │ └── TelemetryTags.cs
│ ├── PersistMessageProcessor/
│ │ ├── Extensions.cs
│ │ ├── IPersistMessageDbContext.cs
│ │ ├── IPersistMessageProcessor.cs
│ │ ├── MessageDeliveryType.cs
│ │ ├── MessageStatus.cs
│ │ ├── PersistMessage.cs
│ │ ├── PersistMessageBackgroundService.cs
│ │ ├── PersistMessageDbContext.cs
│ │ ├── PersistMessageOptions.cs
│ │ └── PersistMessageProcessor.cs
│ ├── Polly/
│ │ └── Extensions.cs
│ ├── ProblemDetails/
│ │ └── Extensions.cs
│ ├── TestBase/
│ │ ├── TestBase.cs
│ │ └── TestContainers.cs
│ ├── Utils/
│ │ ├── NoSynchronizationContextScope.cs
│ │ ├── ServiceLocator.cs
│ │ └── TypeProvider.cs
│ ├── Validation/
│ │ ├── Extensions.cs
│ │ ├── ValidationBehavior.cs
│ │ ├── ValidationError.cs
│ │ └── ValidationResultModel.cs
│ └── Web/
│ ├── ApiVersioningExtensions.cs
│ ├── AppOptions.cs
│ ├── BaseController.cs
│ ├── ConfigurationExtensions.cs
│ ├── ConfigurationHelper.cs
│ ├── CorrelationExtensions.cs
│ ├── CurrentUserProvider.cs
│ ├── EndpointConfig.cs
│ ├── IMinimalEndpoint.cs
│ ├── MinimalApiExtensions.cs
│ ├── ServiceCollectionExtensions.cs
│ ├── ServiceProviderExtensions.cs
│ └── SlugifyParameterTransformer.cs
└── Services/
├── Booking/
│ ├── Dockerfile
│ ├── src/
│ │ ├── Booking/
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── Booking/
│ │ │ │ ├── Dtos/
│ │ │ │ │ └── CreateReservation.cs
│ │ │ │ ├── Exceptions/
│ │ │ │ │ ├── BookingAlreadyExistException.cs
│ │ │ │ │ ├── FlightNotFoundException.cs
│ │ │ │ │ ├── InvalidAircraftIdException.cs
│ │ │ │ │ ├── InvalidArriveAirportIdException.cs
│ │ │ │ │ ├── InvalidDepartureAirportIdException.cs
│ │ │ │ │ ├── InvalidFlightDateException.cs
│ │ │ │ │ ├── InvalidFlightNumberException.cs
│ │ │ │ │ ├── InvalidPassengerNameException.cs
│ │ │ │ │ ├── InvalidPriceException.cs
│ │ │ │ │ └── SeatNumberException.cs
│ │ │ │ ├── Features/
│ │ │ │ │ ├── BookingMappings.cs
│ │ │ │ │ └── CreatingBook/
│ │ │ │ │ └── V1/
│ │ │ │ │ └── CreateBooking.cs
│ │ │ │ ├── Models/
│ │ │ │ │ ├── Booking.cs
│ │ │ │ │ └── BookingReadModel.cs
│ │ │ │ └── ValueObjects/
│ │ │ │ ├── PassengerInfo.cs
│ │ │ │ └── Trip.cs
│ │ │ ├── Booking.csproj
│ │ │ ├── BookingEventMapper.cs
│ │ │ ├── BookingProjection.cs
│ │ │ ├── BookingRoot.cs
│ │ │ ├── Configuration/
│ │ │ │ └── GrpcOptions.cs
│ │ │ ├── Data/
│ │ │ │ └── BookingReadDbContext.cs
│ │ │ ├── Extensions/
│ │ │ │ └── Infrastructure/
│ │ │ │ ├── GrpcClientExtensions.cs
│ │ │ │ ├── InfrastructureExtensions.cs
│ │ │ │ └── MediatRExtensions.cs
│ │ │ └── GrpcClient/
│ │ │ └── Protos/
│ │ │ ├── flight.proto
│ │ │ └── passenger.proto
│ │ └── Booking.Api/
│ │ ├── Booking.Api.csproj
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.docker.json
│ │ ├── appsettings.json
│ │ └── appsettings.test.json
│ └── tests/
│ ├── IntegrationTest/
│ │ ├── Booking/
│ │ │ └── Features/
│ │ │ └── CreateBookingTests.cs
│ │ ├── BookingIntegrationTestBase.cs
│ │ ├── Fakes/
│ │ │ ├── FakeCreateBookingCommand.cs
│ │ │ ├── FakeFlightResponse.cs
│ │ │ ├── FakeGetAvailableSeatsResponse.cs
│ │ │ ├── FakePassengerResponse.cs
│ │ │ └── FakeReserveSeatResponse.cs
│ │ ├── Integration.Test.csproj
│ │ └── xunit.runner.json
│ ├── PerformanceTest/
│ │ ├── .openapi-generator/
│ │ │ ├── FILES
│ │ │ └── VERSION
│ │ ├── .openapi-generator-ignore
│ │ ├── README.md
│ │ └── script.js
│ └── tests.sln
├── Flight/
│ ├── Dockerfile
│ ├── src/
│ │ ├── Flight/
│ │ │ ├── Aircrafts/
│ │ │ │ ├── Dtos/
│ │ │ │ │ └── AircraftDto.cs
│ │ │ │ ├── Exceptions/
│ │ │ │ │ ├── AircraftAlreadyExistException.cs
│ │ │ │ │ ├── InvalidAircraftIdException.cs
│ │ │ │ │ ├── InvalidManufacturingYearException.cs
│ │ │ │ │ ├── InvalidModelException.cs
│ │ │ │ │ └── InvalidNameException.cs
│ │ │ │ ├── Features/
│ │ │ │ │ ├── AircraftMappings.cs
│ │ │ │ │ └── CreatingAircraft/
│ │ │ │ │ └── V1/
│ │ │ │ │ ├── CreateAircraft.cs
│ │ │ │ │ └── CreateAircraftMongo.cs
│ │ │ │ ├── Models/
│ │ │ │ │ ├── Aircraft.cs
│ │ │ │ │ └── AircraftReadModel.cs
│ │ │ │ └── ValueObjects/
│ │ │ │ ├── AircraftId.cs
│ │ │ │ ├── ManufacturingYear.cs
│ │ │ │ ├── Model.cs
│ │ │ │ └── Name.cs
│ │ │ ├── Airports/
│ │ │ │ ├── Dtos/
│ │ │ │ │ └── AirportDto.cs
│ │ │ │ ├── Exceptions/
│ │ │ │ │ ├── AirportAlreadyExistException.cs
│ │ │ │ │ ├── InvalidAddressException.cs
│ │ │ │ │ ├── InvalidAirportIdException.cs
│ │ │ │ │ ├── InvalidCodeException.cs
│ │ │ │ │ └── InvalidNameException.cs
│ │ │ │ ├── Features/
│ │ │ │ │ ├── AirportMappings.cs
│ │ │ │ │ └── CreatingAirport/
│ │ │ │ │ └── V1/
│ │ │ │ │ ├── CreateAirport.cs
│ │ │ │ │ └── CreateAirportMongo.cs
│ │ │ │ ├── Models/
│ │ │ │ │ ├── Airport.cs
│ │ │ │ │ └── AirportReadModel.cs
│ │ │ │ └── ValueObjects/
│ │ │ │ ├── Address.cs
│ │ │ │ ├── AirportId.cs
│ │ │ │ ├── Code.cs
│ │ │ │ └── Name.cs
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── Data/
│ │ │ │ ├── Configurations/
│ │ │ │ │ ├── AircraftConfiguration.cs
│ │ │ │ │ ├── AirportConfiguration.cs
│ │ │ │ │ ├── FlightConfiguration.cs
│ │ │ │ │ └── SeatConfiguration.cs
│ │ │ │ ├── DesignTimeDbContextFactory.cs
│ │ │ │ ├── FlightDbContext.cs
│ │ │ │ ├── FlightReadDbContext.cs
│ │ │ │ ├── Migrations/
│ │ │ │ │ ├── 20230611230948_initial.Designer.cs
│ │ │ │ │ ├── 20230611230948_initial.cs
│ │ │ │ │ └── FlightDbContextModelSnapshot.cs
│ │ │ │ ├── Seed/
│ │ │ │ │ ├── FlightDataSeeder.cs
│ │ │ │ │ └── InitialData.cs
│ │ │ │ └── readme.md
│ │ │ ├── Extensions/
│ │ │ │ └── Infrastructure/
│ │ │ │ ├── InfrastructureExtensions.cs
│ │ │ │ └── MediatRExtensions.cs
│ │ │ ├── Flight.csproj
│ │ │ ├── FlightEventMapper.cs
│ │ │ ├── FlightRoot.cs
│ │ │ ├── Flights/
│ │ │ │ ├── Dtos/
│ │ │ │ │ └── FlightDto.cs
│ │ │ │ ├── Enums/
│ │ │ │ │ └── FlightStatus.cs
│ │ │ │ ├── Exceptions/
│ │ │ │ │ ├── FlightAlreadyExistException.cs
│ │ │ │ │ ├── FlightNotFountException.cs
│ │ │ │ │ ├── InvalidArriveDateException.cs
│ │ │ │ │ ├── InvalidDepartureDateException.cs
│ │ │ │ │ ├── InvalidDurationException.cs
│ │ │ │ │ ├── InvalidFlightDateException.cs
│ │ │ │ │ ├── InvalidFlightIdException.cs
│ │ │ │ │ ├── InvalidFlightNumberException.cs
│ │ │ │ │ └── InvalidPriceException.cs
│ │ │ │ ├── Features/
│ │ │ │ │ ├── CreatingFlight/
│ │ │ │ │ │ └── V1/
│ │ │ │ │ │ ├── CreateFlight.cs
│ │ │ │ │ │ └── CreateFlightMongo.cs
│ │ │ │ │ ├── DeletingFlight/
│ │ │ │ │ │ └── V1/
│ │ │ │ │ │ ├── DeleteFlight.cs
│ │ │ │ │ │ └── DeleteFlightMongo.cs
│ │ │ │ │ ├── FlightMappings.cs
│ │ │ │ │ ├── GettingAvailableFlights/
│ │ │ │ │ │ └── V1/
│ │ │ │ │ │ └── GetAvailableFlights.cs
│ │ │ │ │ ├── GettingFlightById/
│ │ │ │ │ │ └── V1/
│ │ │ │ │ │ └── GetFlightById.cs
│ │ │ │ │ └── UpdatingFlight/
│ │ │ │ │ └── V1/
│ │ │ │ │ ├── UpdateFlight.cs
│ │ │ │ │ └── UpdateFlightMongo.cs
│ │ │ │ ├── Models/
│ │ │ │ │ ├── Flight.cs
│ │ │ │ │ └── FlightReadModel.cs
│ │ │ │ └── ValueObjects/
│ │ │ │ ├── ArriveDate.cs
│ │ │ │ ├── DepartureDate.cs
│ │ │ │ ├── DurationMinutes.cs
│ │ │ │ ├── FlightDate.cs
│ │ │ │ ├── FlightId.cs
│ │ │ │ ├── FlightNumber.cs
│ │ │ │ └── Price.cs
│ │ │ ├── GrpcServer/
│ │ │ │ ├── Protos/
│ │ │ │ │ └── flight.proto
│ │ │ │ └── Services/
│ │ │ │ └── FlightGrpcServices.cs
│ │ │ └── Seats/
│ │ │ ├── Dtos/
│ │ │ │ └── SeatDto.cs
│ │ │ ├── Enums/
│ │ │ │ ├── SeatClass.cs
│ │ │ │ └── SeatType.cs
│ │ │ ├── Exceptions/
│ │ │ │ ├── AllSeatsFullException.cs
│ │ │ │ ├── InvalidSeatIdException.cs
│ │ │ │ ├── InvalidSeatNumberException.cs
│ │ │ │ ├── SeatAlreadyExistException.cs
│ │ │ │ └── SeatNumberIncorrectException.cs
│ │ │ ├── Features/
│ │ │ │ ├── CreatingSeat/
│ │ │ │ │ └── V1/
│ │ │ │ │ ├── CreateSeat.cs
│ │ │ │ │ └── CreateSeatMongo.cs
│ │ │ │ ├── GettingAvailableSeats/
│ │ │ │ │ └── V1/
│ │ │ │ │ └── GetAvailableSeats.cs
│ │ │ │ ├── ReservingSeat/
│ │ │ │ │ └── V1/
│ │ │ │ │ ├── ReserveSeat.cs
│ │ │ │ │ └── ReserveSeatMongo.cs
│ │ │ │ └── SeatMappings.cs
│ │ │ ├── Models/
│ │ │ │ ├── Seat.cs
│ │ │ │ └── SeatReadModel.cs
│ │ │ └── ValueObjects/
│ │ │ ├── SeatId.cs
│ │ │ └── SeatNumber.cs
│ │ └── Flight.Api/
│ │ ├── Flight.Api.csproj
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.docker.json
│ │ ├── appsettings.json
│ │ └── appsettings.test.json
│ └── tests/
│ ├── EndToEndTest/
│ │ ├── EndToEnd.Test.csproj
│ │ ├── Fakes/
│ │ │ ├── FakeCreateFlightCommand.cs
│ │ │ └── FakeCreateFlightMongoCommand.cs
│ │ ├── Flight/
│ │ │ └── Features/
│ │ │ ├── CreateFlightTests.cs
│ │ │ └── GetFlightByIdTests.cs
│ │ ├── FlightEndToEndTestBase.cs
│ │ ├── FlightTestDataSeeder.cs
│ │ ├── Routes/
│ │ │ └── ApiRoutes.cs
│ │ └── xunit.runner.json
│ ├── IntegrationTest/
│ │ ├── Aircraft/
│ │ │ └── Features/
│ │ │ └── CreateAircraftTests.cs
│ │ ├── Airport/
│ │ │ └── Features/
│ │ │ └── CreateAirportTests.cs
│ │ ├── Fakes/
│ │ │ ├── FakeCreateAircraftCommand.cs
│ │ │ ├── FakeCreateAirportCommand.cs
│ │ │ ├── FakeCreateFlightCommand.cs
│ │ │ ├── FakeCreateFlightMongoCommand.cs
│ │ │ ├── FakeCreateSeatCommand.cs
│ │ │ ├── FakeCreateSeatMongoCommand.cs
│ │ │ └── FakeUpdateFlightCommand.cs
│ │ ├── Flight/
│ │ │ └── Features/
│ │ │ ├── CreateFlightTests.cs
│ │ │ ├── DeleteFlightTests.cs
│ │ │ ├── GetAvailableFlightsTests.cs
│ │ │ ├── GetFlightByIdTests.cs
│ │ │ └── UpdateFlightTests.cs
│ │ ├── FlightIntegrationTestBase.cs
│ │ ├── FlightTestDataSeeder.cs
│ │ ├── Integration.Test.csproj
│ │ ├── Seat/
│ │ │ └── Features/
│ │ │ ├── GetAvailableSeatsTests.cs
│ │ │ └── ReserveSeatTests.cs
│ │ └── xunit.runner.json
│ ├── PerformanceTest/
│ │ ├── .openapi-generator/
│ │ │ ├── FILES
│ │ │ └── VERSION
│ │ ├── .openapi-generator-ignore
│ │ ├── README.md
│ │ └── script.js
│ ├── UnitTest/
│ │ ├── Aircraft/
│ │ │ └── Features/
│ │ │ └── CreateAircraftTests/
│ │ │ ├── CreateAircraftCommandHandlerTests.cs
│ │ │ └── CreateAircraftCommandValidatorTests.cs
│ │ ├── Airport/
│ │ │ └── Features/
│ │ │ └── CreateAirportTests/
│ │ │ ├── CreateAirportCommandHandlerTests.cs
│ │ │ └── CreateAirportCommandValidatorTests.cs
│ │ ├── Common/
│ │ │ ├── DbContextFactory.cs
│ │ │ ├── MapperFactory.cs
│ │ │ └── UnitTestFixture.cs
│ │ ├── Fakes/
│ │ │ ├── FakeCreateAircraftCommand.cs
│ │ │ ├── FakeCreateAirportCommand.cs
│ │ │ ├── FakeCreateFlightCommand.cs
│ │ │ ├── FakeCreateSeatCommand.cs
│ │ │ ├── FakeFlightCreate.cs
│ │ │ ├── FakeFlightUpdate.cs
│ │ │ ├── FakeValidateCreateAircraftCommand.cs
│ │ │ ├── FakeValidateCreateAirportCommand.cs
│ │ │ ├── FakeValidateCreateFlightCommand.cs
│ │ │ └── FakeValidateCreateSeatCommand.cs
│ │ ├── Flight/
│ │ │ ├── Features/
│ │ │ │ ├── Domains/
│ │ │ │ │ ├── CreateFlightTests.cs
│ │ │ │ │ └── UpdateFlightTests.cs
│ │ │ │ └── Handlers/
│ │ │ │ └── CreateFlight/
│ │ │ │ ├── CreateFlightCommandHandlerTests.cs
│ │ │ │ └── CreateFlightCommandValidatorTests.cs
│ │ │ └── FlightMappingTests.cs
│ │ ├── Seat/
│ │ │ ├── Features/
│ │ │ │ ├── CreateSeatCommandHandlerTests.cs
│ │ │ │ └── CreateSeatCommandValidatorTests.cs
│ │ │ └── SeatMappingTests.cs
│ │ ├── Unit.Test.csproj
│ │ └── xunit.runner.json
│ └── tests.sln
├── Identity/
│ ├── Dockerfile
│ ├── src/
│ │ ├── Identity/
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── Configurations/
│ │ │ │ ├── AuthOptions.cs
│ │ │ │ ├── Config.cs
│ │ │ │ └── UserValidator.cs
│ │ │ ├── Data/
│ │ │ │ ├── Configurations/
│ │ │ │ │ ├── RoleClaimConfiguration.cs
│ │ │ │ │ ├── RoleConfiguration.cs
│ │ │ │ │ ├── UserClaimConfiguration.cs
│ │ │ │ │ ├── UserConfiguration.cs
│ │ │ │ │ ├── UserLoginConfiguration.cs
│ │ │ │ │ ├── UserRoleConfiguration.cs
│ │ │ │ │ └── UserTokenConfiguration.cs
│ │ │ │ ├── DesignTimeDbContextFactory.cs
│ │ │ │ ├── IdentityContext.cs
│ │ │ │ ├── Migrations/
│ │ │ │ │ ├── 20230331193410_initial.Designer.cs
│ │ │ │ │ ├── 20230331193410_initial.cs
│ │ │ │ │ └── IdentityContextModelSnapshot.cs
│ │ │ │ ├── Seed/
│ │ │ │ │ ├── IdentityDataSeeder.cs
│ │ │ │ │ └── InitialData.cs
│ │ │ │ └── readme.md
│ │ │ ├── Extensions/
│ │ │ │ └── Infrastructure/
│ │ │ │ ├── IdentityServerExtensions.cs
│ │ │ │ ├── InfrastructureExtensions.cs
│ │ │ │ └── MediatRExtensions.cs
│ │ │ ├── Identity/
│ │ │ │ ├── Constants/
│ │ │ │ │ └── Constants.cs
│ │ │ │ ├── Exceptions/
│ │ │ │ │ └── RegisterIdentityUserException.cs
│ │ │ │ ├── Features/
│ │ │ │ │ ├── IdentityMappings.cs
│ │ │ │ │ └── RegisteringNewUser/
│ │ │ │ │ └── V1/
│ │ │ │ │ └── RegisterNewUser.cs
│ │ │ │ └── Models/
│ │ │ │ ├── Role.cs
│ │ │ │ ├── RoleClaim.cs
│ │ │ │ ├── User.cs
│ │ │ │ ├── UserClaim.cs
│ │ │ │ ├── UserLogin.cs
│ │ │ │ ├── UserRole.cs
│ │ │ │ └── UserToken.cs
│ │ │ ├── Identity.csproj
│ │ │ ├── IdentityEventMapper.cs
│ │ │ └── IdentityRoot.cs
│ │ └── Identity.Api/
│ │ ├── Identity.Api.csproj
│ │ ├── Program.cs
│ │ ├── Properties/
│ │ │ └── launchSettings.json
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.docker.json
│ │ ├── appsettings.json
│ │ ├── appsettings.test.json
│ │ └── keys/
│ │ ├── is-signing-key-0AC3347A09AA5E44E947F3E30ED54871.json
│ │ ├── is-signing-key-A57781A0405849BDE786A79636460E49.json
│ │ ├── is-signing-key-B3C31EEE2718D3C5004C6E85AD74F26C.json
│ │ └── is-signing-key-E1668D5B7CCDD18C610506FCA7C5D194.json
│ └── tests/
│ ├── IntegrationTest/
│ │ ├── Fakes/
│ │ │ └── FakeRegisterNewUserCommand.cs
│ │ ├── Identity/
│ │ │ └── Features/
│ │ │ └── RegisterNewUserTests.cs
│ │ ├── IdentityIntegrationTestBase.cs
│ │ ├── IdentityTestDataSeeder.cs
│ │ ├── Integration.Test.csproj
│ │ └── xunit.runner.json
│ ├── PerformanceTest/
│ │ ├── .openapi-generator/
│ │ │ ├── FILES
│ │ │ └── VERSION
│ │ ├── .openapi-generator-ignore
│ │ ├── README.md
│ │ └── script.js
│ └── tests.sln
└── Passenger/
├── Dockerfile
├── src/
│ ├── Passenger/
│ │ ├── AssemblyInfo.cs
│ │ ├── Data/
│ │ │ ├── Configurations/
│ │ │ │ └── PassengerConfiguration.cs
│ │ │ ├── DesignTimeDbContextFactory.cs
│ │ │ ├── Migrations/
│ │ │ │ ├── 20230611213031_initial.Designer.cs
│ │ │ │ ├── 20230611213031_initial.cs
│ │ │ │ └── PassengerDbContextModelSnapshot.cs
│ │ │ ├── PassengerDbContext.cs
│ │ │ ├── PassengerReadDbContext.cs
│ │ │ └── readme.md
│ │ ├── Exceptions/
│ │ │ ├── InvalidAgeException.cs
│ │ │ ├── InvalidNameException.cs
│ │ │ ├── InvalidPassengerIdException.cs
│ │ │ ├── InvalidPassportNumberException.cs
│ │ │ ├── PassengerAlreadyExist.cs
│ │ │ └── PassengerNotFoundException.cs
│ │ ├── Extensions/
│ │ │ └── Infrastructure/
│ │ │ ├── InfrastructureExtensions.cs
│ │ │ └── MediatRExtensions.cs
│ │ ├── GrpcServer/
│ │ │ ├── Protos/
│ │ │ │ └── passenger.proto
│ │ │ └── Services/
│ │ │ └── PassengerGrpcServices.cs
│ │ ├── Identity/
│ │ │ └── Consumers/
│ │ │ └── RegisteringNewUser/
│ │ │ └── V1/
│ │ │ ├── PassengerCreatedDomainEvent.cs
│ │ │ └── RegisterNewUser.cs
│ │ ├── Passenger.csproj
│ │ ├── PassengerEventMapper.cs
│ │ ├── PassengerRoot.cs
│ │ └── Passengers/
│ │ ├── Dtos/
│ │ │ └── PassengerDto.cs
│ │ ├── Enums/
│ │ │ └── PassengerType.cs
│ │ ├── Exceptions/
│ │ │ ├── InvalidAgeException.cs
│ │ │ ├── InvalidNameException.cs
│ │ │ ├── InvalidPassportNumberException.cs
│ │ │ ├── PassengerAlreadyExist.cs
│ │ │ └── PassengerNotFoundException.cs
│ │ ├── Features/
│ │ │ ├── CompletingRegisterPassenger/
│ │ │ │ └── V1/
│ │ │ │ ├── CompleteRegisterPassenger.cs
│ │ │ │ └── CompleteRegisterPassengerMongo.cs
│ │ │ ├── GettingPassengerById/
│ │ │ │ └── V1/
│ │ │ │ └── GetPassengerById.cs
│ │ │ └── PassengerMappings.cs
│ │ ├── Models/
│ │ │ ├── Passenger.cs
│ │ │ └── PassengerReadModel.cs
│ │ └── ValueObjects/
│ │ ├── Age.cs
│ │ ├── Name.cs
│ │ ├── PassengerId.cs
│ │ └── PassportNumber.cs
│ └── Passenger.Api/
│ ├── Passenger.Api.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── appsettings.Development.json
│ ├── appsettings.docker.json
│ ├── appsettings.json
│ └── appsettings.test.json
└── tests/
├── IntegrationTest/
│ ├── Fakes/
│ │ ├── FakeCompleteRegisterPassengerCommand.cs
│ │ └── FakeCompleteRegisterPassengerMongoCommand.cs
│ ├── Integration.Test.csproj
│ ├── Passenger/
│ │ └── Features/
│ │ ├── CompleteRegisterPassengerTests.cs
│ │ └── GetPassengerByIdTests.cs
│ ├── PassengerIntegrationTestBase.cs
│ └── xunit.runner.json
├── PerformanceTest/
│ ├── .openapi-generator/
│ │ ├── FILES
│ │ └── VERSION
│ ├── .openapi-generator-ignore
│ ├── README.md
│ └── script.js
└── tests.sln
================================================
FILE CONTENTS
================================================
================================================
FILE: .aspire/settings.json
================================================
{
"appHostPath": "../src/Aspire/src/AppHost/AppHost.csproj"
}
================================================
FILE: .config/dotnet-tools.json
================================================
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-outdated-tool": {
"version": "4.6.9",
"commands": [
"dotnet-outdated"
]
},
"dotnet-ef": {
"version": "10.0.3",
"commands": [
"dotnet-ef"
]
},
"aspire.cli": {
"version": "13.1.1",
"commands": [
"aspire"
]
},
"csharpier": {
"version": "0.30.6",
"commands": [
"dotnet-csharpier"
]
}
}
}
================================================
FILE: .dockerignore
================================================
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin/
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj/
**/.tye/
**/secrets.dev.yaml
**/values.dev.yaml
**/*.jwk
**/keys
LICENSE
README.md
CHANGELOG.md
================================================
FILE: .editorconfig
================================================
# https://editorconfig.org
# https://www.jetbrains.com/help/resharper/Using_EditorConfig.html
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files
# When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory. A search for .editorconfig files will stop if the root filepath is reached or an EditorConfig file with `root=true` is found.
# Remove the line below if you want to inherit .editorconfig settings from higher directories
##################################################################################
## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
## Microsoft Rules
root = true
# All files
[*]
indent_style = space
# Xml files
[*.xml]
indent_size = 2
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
tab_width = 4
max_line_length = 120
# New line preferences
insert_final_newline = false
#### .NET Coding Conventions ####
[*.{cs,vb}]
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?#enable-on-build
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/language-rules#option-format
# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
#### C# Coding Conventions ####
[*.cs]
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_anonymous_function = true:suggestion
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_prefer_top_level_statements = true:silent
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
[*.{cs,vb}]
# Naming rules
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
# Symbol specifications
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interfaces.required_modifiers =
dotnet_naming_symbols.enums.applicable_kinds = enum
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.enums.required_modifiers =
dotnet_naming_symbols.events.applicable_kinds = event
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.events.required_modifiers =
dotnet_naming_symbols.methods.applicable_kinds = method
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.methods.required_modifiers =
dotnet_naming_symbols.properties.applicable_kinds = property
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.properties.required_modifiers =
dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_fields.required_modifiers =
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters.required_modifiers =
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
dotnet_naming_symbols.local_variables.applicable_kinds = local
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
dotnet_naming_symbols.local_variables.required_modifiers =
dotnet_naming_symbols.local_constants.applicable_kinds = local
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
dotnet_naming_symbols.local_constants.required_modifiers = const
dotnet_naming_symbols.parameters.applicable_kinds = parameter
dotnet_naming_symbols.parameters.applicable_accessibilities = *
dotnet_naming_symbols.parameters.required_modifiers =
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_constant_fields.required_modifiers = const
dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
dotnet_naming_style.pascalcase.required_prefix =
dotnet_naming_style.pascalcase.required_suffix =
dotnet_naming_style.pascalcase.word_separator =
dotnet_naming_style.pascalcase.capitalization = pascal_case
dotnet_naming_style.ipascalcase.required_prefix = I
dotnet_naming_style.ipascalcase.required_suffix =
dotnet_naming_style.ipascalcase.word_separator =
dotnet_naming_style.ipascalcase.capitalization = pascal_case
dotnet_naming_style.tpascalcase.required_prefix = T
dotnet_naming_style.tpascalcase.required_suffix =
dotnet_naming_style.tpascalcase.word_separator =
dotnet_naming_style.tpascalcase.capitalization = pascal_case
dotnet_naming_style._camelcase.required_prefix = _
dotnet_naming_style._camelcase.required_suffix =
dotnet_naming_style._camelcase.word_separator =
dotnet_naming_style._camelcase.capitalization = camel_case
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.s_camelcase.required_prefix = s_
dotnet_naming_style.s_camelcase.required_suffix =
dotnet_naming_style.s_camelcase.word_separator =
dotnet_naming_style.s_camelcase.capitalization = camel_case
##################################################################################
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
##################################################################################
## Roslyn Code quality rules
dotnet_diagnostic.CA1030.severity = none
dotnet_diagnostic.CA1034.severity = none
dotnet_diagnostic.CA1062.severity = suggestion
dotnet_code_quality.CA1062.exclude_extension_method_this_parameter = true
dotnet_code_quality.exclude_extension_method_this_parameter = true
dotnet_code_quality.null_check_validation_methods = ThrowIfArgumentIsNull
# CA1031: Do not catch general exception types
dotnet_diagnostic.CA1031.severity = none
# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = none
# CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = error
# CA1307: Specify StringComparison for clarity
dotnet_diagnostic.CA1307.severity = error
# CA1308: Normalize strings to uppercase
dotnet_diagnostic.CA1308.severity = none
# CA1309: Use ordinal StringComparison
dotnet_diagnostic.CA1309.severity = error
# CA1724: Type names should not match namespaces
dotnet_diagnostic.CA1724.severity = none
# CA1819: Properties should not return arrays
dotnet_diagnostic.CA1819.severity = none
# CA1851: Possible multiple enumerations of IEnumerable collection. Related to GH-issue #2000
dotnet_diagnostic.CA1851.severity = suggestion
# CA1859: Use concrete types when possible for improved performance
dotnet_diagnostic.CA1859.severity = suggestion
# CA1860: Avoid using 'Enumerable.Any()' extension method
dotnet_diagnostic.CA1860.severity = warning
# CA1861: Avoid constant arrays as arguments
dotnet_diagnostic.CA1861.severity = none
# CA2007: Do not directly await a Task
dotnet_diagnostic.CA2007.severity = none
# CA2225: Operator overloads have named alternates
dotnet_diagnostic.CA2225.severity = none
# CA3075: Insecure DTD Processing
dotnet_diagnostic.CA3075.severity = none
# CA5369: Use XmlReader for Deserialize
dotnet_diagnostic.CA5369.severity = none
# CA1305: Specify IFormatProvider
dotnet_diagnostic.CA1305.severity = None
# CA1063: Implement IDisposable correctly
dotnet_diagnostic.CA1063.severity = None
# CA2201: Do not raise reserved exception types
dotnet_diagnostic.ca2201.severity = Suggestion
# CA1848: Use the LoggerMessage delegates
dotnet_diagnostic.ca1848.severity = Suggestion
# CA1810: Initialize reference type static fields inline
dotnet_diagnostic.ca1810.severity = Suggestion
# CA1725: Parameter names should match base declaration
dotnet_diagnostic.ca1725.severity = Suggestion
# CA1515: Consider making public types internal
dotnet_diagnostic.CA1515.severity = None
# CA2000: Dispose objects before losing scope
dotnet_diagnostic.CA2000.severity = Suggestion
# CA1707: Identifiers should not contain underscores
dotnet_diagnostic.CA1707.severity = None
# CA1716: Identifiers should not match keywords
dotnet_diagnostic.CA1716.severity = Suggestion
# CA1032: Implement standard exception constructors
dotnet_diagnostic.CA1032.severity = Suggestion
# AV1500: A method should not exceed a predefined number (60-100 lines) of lines
dotnet_diagnostic.AV1500.severity = none
##################################################################################
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/
##################################################################################
## Roslyn Code-style rules
dotnet_diagnostic.IDE0048.severity = Suggestion
dotnet_diagnostic.IDE0028.severity = Suggestion
dotnet_diagnostic.IDE0029.severity = Suggestion
dotnet_diagnostic.IDE0030 .severity = Suggestion
dotnet_diagnostic.IDE0004.severity = error
# IDE0005: Remove unnecessary usings/imports
dotnet_diagnostic.IDE0005.severity = warning
# IDE0051: Remove unused private members (no reads or writes)
dotnet_diagnostic.IDE0051.severity = Suggestion
# IDE0052: Remove unread private members (writes but no reads)
dotnet_diagnostic.IDE0052.severity = warning
# Remove unnecessary using directives (IDE0005)
dotnet_diagnostic.IDE0005.severity = none
# CS1574: XML comment on 'construct' has syntactically incorrect cref attribute 'name'
dotnet_diagnostic.CS1574.severity = error
# IDE0055: Fix formatting
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/csharp-formatting-options
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/dotnet-formatting-options
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0055
# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules
dotnet_diagnostic.IDE0055.severity = none
##################################################################################
# https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html
# https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html
## Resharper
# ReSharper properties
resharper_align_linq_query = true
resharper_align_multiline_array_and_object_initializer = true
resharper_align_multiline_binary_patterns = true
resharper_align_multiline_expression = true
resharper_align_multiline_extends_list = true
resharper_align_multiline_parameter = true
resharper_align_multiline_property_pattern = true
resharper_align_multiline_switch_expression = true
resharper_align_multiple_declaration = true
resharper_align_multline_type_parameter_constrains = true
resharper_align_multline_type_parameter_list = true
resharper_align_tuple_components = true
resharper_braces_for_for = required_for_multiline
resharper_braces_for_foreach = required_for_multiline
resharper_braces_for_ifelse = required_for_multiline
resharper_csharp_alignment_tab_fill_style = optimal_fill
resharper_csharp_indent_type_constraints = false
resharper_csharp_int_align_fix_in_adjacent = false
resharper_csharp_outdent_commas = true
resharper_csharp_stick_comment = false
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_after_invocation_lpar = true
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_before_declaration_rpar = true
resharper_csharp_wrap_before_first_type_parameter_constraint = true
resharper_csharp_wrap_before_ternary_opsigns = false
resharper_csharp_wrap_extends_list_style = chop_if_long
resharper_csharp_wrap_multiple_declaration_style = wrap_if_long
resharper_csharp_wrap_multiple_type_parameter_constraints_style = chop_always
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_enforce_line_ending_style = true
resharper_indent_anonymous_method_block = true
resharper_indent_braces_inside_statement_conditions = false
resharper_indent_nested_fixed_stmt = true
resharper_indent_nested_foreach_stmt = true
resharper_indent_nested_for_stmt = true
resharper_indent_nested_lock_stmt = true
resharper_indent_nested_usings_stmt = true
resharper_indent_nested_while_stmt = true
resharper_keep_existing_declaration_block_arrangement = true
resharper_keep_existing_declaration_parens_arrangement = false
resharper_keep_existing_embedded_arrangement = false
resharper_keep_existing_embedded_block_arrangement = true
resharper_keep_existing_enum_arrangement = true
resharper_keep_existing_invocation_parens_arrangement = false
resharper_keep_existing_property_patterns_arrangement = false
resharper_keep_existing_switch_expression_arrangement = false
resharper_max_array_initializer_elements_on_line = 700
resharper_max_formal_parameters_on_line = 500
resharper_max_invocation_arguments_on_line = 700
resharper_new_line_before_while = true
resharper_place_attribute_on_same_line = false
resharper_place_linq_into_on_new_line = false
resharper_place_simple_case_statement_on_same_line = if_owner_is_single_line
resharper_place_simple_property_pattern_on_single_line = false
resharper_show_autodetect_configure_formatting_tip = false
resharper_space_within_single_line_array_initializer_braces = false
resharper_trailing_comma_in_multiline_lists = true
resharper_trailing_comma_in_singleline_lists = true
resharper_use_heuristics_for_body_style = false
resharper_use_indent_from_vs = false
resharper_use_roslyn_logic_for_evident_types = true
resharper_wrap_array_initializer_style = chop_always
resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
resharper_wrap_for_stmt_header_style = wrap_if_long
resharper_wrap_switch_expression = chop_if_long
resharper_wrap_verbatim_interpolated_strings = chop_if_long
# ReSharper inspection severities
resharper_arrange_accessor_owner_body_highlighting = none
resharper_arrange_redundant_parentheses_highlighting = hint
resharper_arrange_type_member_modifiers_highlighting = hint
resharper_arrange_type_modifiers_highlighting = hint
resharper_check_namespace_highlighting = none
resharper_enforce_if_statement_braces_highlighting = hint
resharper_inconsistent_naming_highlighting = suggestion
resharper_static_member_in_generic_type_highlighting = none
resharper_suggest_var_or_type_built_in_types_highlighting = hint
resharper_suggest_var_or_type_elsewhere_highlighting = hint
resharper_suggest_var_or_type_simple_types_highlighting = hint
resharper_web_config_module_not_resolved_highlighting = warning
resharper_web_config_type_not_resolved_highlighting = warning
resharper_web_config_wrong_module_highlighting = warning
# https://www.jetbrains.com/help/rider/ClassNeverInstantiated.Global.html
resharper_class_never_instantiated_global_highlighting = none
# Convert lambda expression to method group
resharper_convert_closure_to_method_group_highlighting = none
# Start each element in a object or collection initializer on a new line
resharper_wrap_object_and_collection_initializer_style = chop_always
# Force an empty line
resharper_blank_lines_after_multiline_statements = 1
# Don't remove existing line breaks
resharper_keep_existing_initializer_arrangement = true
resharper_keep_existing_arrangement = true
# We care about that extra else after an else-if
resharper_redundant_if_else_block_highlighting = none
# Don't remove explicit default cases in switch statements
resharper_redundant_empty_switch_section_highlighting = none
resharper_align_multiline_binary_expressions_chain = false
# Only use new() when the type is obvious
resharper_object_creation_when_type_not_evident = explicitly_typed
resharper_object_creation_when_type_evident = target_typed
# Indent 4 spaces per necessary indention
resharper_continuous_indent_multiplier = 1
# Avoid breaking a generic definition
resharper_wrap_before_extends_colon = true
resharper_blank_lines_before_multiline_statements = 1
resharper_parentheses_non_obvious_operations = arithmetic, multiplicative, equality, relational, additive
resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence
##################################################################################
## https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation
## https://documentation.help/StyleCop/StyleCop.html
## StyleCop.Analyzers
##################################################################################
# Using directive should appear within a namespace declaration
dotnet_diagnostic.sa1200.severity = None
# Generic type parameter documentation should have text.
dotnet_diagnostic.sa1622.severity = None
# XML comment analysis is disabled due to project configuration
dotnet_diagnostic.sa0001.severity = None
# The file header is missing or not located at the top of the file
dotnet_diagnostic.sa1633.severity = None
# Use string.Empty for empty strings
dotnet_diagnostic.sa1122.severity = None
# Variable '_' should begin with lower-case letter
dotnet_diagnostic.sa1312.severity = None
# Parameter '_' should begin with lower-case letter
dotnet_diagnostic.sa1313.severity = None
# Elements should be documented
dotnet_diagnostic.sa1600.severity = None
# Prefix local calls with this
dotnet_diagnostic.sa1101.severity = None
# 'public' members should come before 'private' members
dotnet_diagnostic.sa1202.severity = None
# Comments should contain text
dotnet_diagnostic.sa1120.severity = None
# Constant fields should appear before non-constant fields
dotnet_diagnostic.sa1203.severity = None
# Field '_blah' should not begin with an underscore
dotnet_diagnostic.sa1309.severity = None
# Use trailing comma in multi-line initializers
dotnet_diagnostic.sa1413.severity = None
# A method should not follow a class
dotnet_diagnostic.sa1201.severity = None
# Elements should be separated by blank line
dotnet_diagnostic.sa1516.severity = None
# The parameter spans multiple lines
dotnet_diagnostic.sa1118.severity = None
# Static members should appear before non-static members
dotnet_diagnostic.sa1204.severity = None
# Put constructor initializers on their own line
dotnet_diagnostic.sa1128.severity = None
# Opening braces should not be preceded by blank line
dotnet_diagnostic.sa1509.severity = None
# The parameter should begin on the line after the previous parameter
dotnet_diagnostic.sa1115.severity = None
# File name should match first type name
dotnet_diagnostic.sa1649.severity = None
# File may only contain a single type
dotnet_diagnostic.sa1402.severity = None
# Enumeration items should be documented
dotnet_diagnostic.sa1602.severity = None
# Element should not be on a single line
dotnet_diagnostic.sa1502.severity = None
# Closing parenthesis should not be preceded by a space
dotnet_diagnostic.sa1009.severity = None
# Closing parenthesis should be on line of last parameter
dotnet_diagnostic.sa1111.severity = None
# Braces should not be ommitted
dotnet_diagnostic.sa1503.severity = None
dotnet_diagnostic.sa1401.severity = None
# The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line.
# dotnet_diagnostic.SA1117.severity = Suggestion
# The parameters to a C# method or indexer call or declaration span across multiple lines, but the first parameter does not start on the line after the opening bracket.
# dotnet_diagnostic.SA1116.severity = Suggestion
# A C# partial element is missing a documentation header.
dotnet_diagnostic.sa1601.severity = None
# A tag within a C# element’s documentation header is empty.
dotnet_diagnostic.sa1614.severity = None
# A C# element is missing documentation for its return value.
dotnet_diagnostic.sa1615.severity = None
# The tag within a C# element’s documentation header is empty.
dotnet_diagnostic.sa1616.severity = None
# An opening brace within a C# element is not spaced correctly.
dotnet_diagnostic.sa1012.severity = Suggestion
# A closing brace within a C# element is not spaced correctly.
dotnet_diagnostic.sa1013.severity = Suggestion
# A call to an instance member of the local class or a base class is not prefixed with 'this.', within a C# code file.
dotnet_diagnostic.sa1101.severity = None
# The keywords within the declaration of an element do not follow a standard ordering scheme.
dotnet_diagnostic.SA1206.severity = None
dotnet_diagnostic.SA1106.severity = None
# https://csharpier.com/docs/IntegratingWithLinters#stylecopanalyzers
# IDE0055: Fix formatting
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/csharp-formatting-options
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/dotnet-formatting-options
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0055
# StyleCopAnalyzers
dotnet_diagnostic.SA1000.severity = none
dotnet_diagnostic.SA1009.severity = none
dotnet_diagnostic.SA1111.severity = none
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1137.severity = none
dotnet_diagnostic.SA1413.severity = none
dotnet_diagnostic.SA1500.severity = none
dotnet_diagnostic.SA1501.severity = none
dotnet_diagnostic.SA1502.severity = none
dotnet_diagnostic.SA1504.severity = none
dotnet_diagnostic.SA1515.severity = none
dotnet_diagnostic.SA1516.severity = none
# for csharpier <= 0.21.0
dotnet_diagnostic.SA1127.severity = none
dotnet_diagnostic.SA1128.severity = none
dotnet_diagnostic.SA1001.severity = none
dotnet_diagnostic.SA1002.severity = none
dotnet_diagnostic.SA1003.severity = none
dotnet_diagnostic.SA1007.severity = none
dotnet_diagnostic.SA1008.severity = none
dotnet_diagnostic.SA1010.severity = none
dotnet_diagnostic.SA1011.severity = none
dotnet_diagnostic.SA1012.severity = none
dotnet_diagnostic.SA1013.severity = none
dotnet_diagnostic.SA1014.severity = none
dotnet_diagnostic.SA1015.severity = none
dotnet_diagnostic.SA1016.severity = none
dotnet_diagnostic.SA1017.severity = none
dotnet_diagnostic.SA1018.severity = none
dotnet_diagnostic.SA1019.severity = none
dotnet_diagnostic.SA1020.severity = none
dotnet_diagnostic.SA1021.severity = none
dotnet_diagnostic.SA1022.severity = none
dotnet_diagnostic.SA1023.severity = none
dotnet_diagnostic.SA1024.severity = none
dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1026.severity = none
dotnet_diagnostic.SA1027.severity = none
dotnet_diagnostic.SA1028.severity = none
dotnet_diagnostic.SA1102.severity = none
dotnet_diagnostic.SA1103.severity = none
dotnet_diagnostic.SA1104.severity = none
dotnet_diagnostic.SA1105.severity = none
dotnet_diagnostic.SA1107.severity = none
dotnet_diagnostic.SA1110.severity = none
dotnet_diagnostic.SA1112.severity = none
dotnet_diagnostic.SA1113.severity = none
dotnet_diagnostic.SA1114.severity = none
dotnet_diagnostic.SA1115.severity = none
dotnet_diagnostic.SA1116.severity = none
dotnet_diagnostic.SA1117.severity = none
dotnet_diagnostic.SA1127.severity = none
dotnet_diagnostic.SA1128.severity = none
dotnet_diagnostic.SA1136.severity = none
dotnet_diagnostic.SA1505.severity = none
dotnet_diagnostic.SA1506.severity = none
dotnet_diagnostic.SA1507.severity = none
dotnet_diagnostic.SA1508.severity = none
dotnet_diagnostic.SA1509.severity = none
dotnet_diagnostic.SA1510.severity = none
dotnet_diagnostic.SA1511.severity = none
dotnet_diagnostic.SA1517.severity = none
dotnet_diagnostic.SA1518.severity = none
##################################################################################
## https://github.com/meziantou/Meziantou.Analyzer/tree/main/docs
## Meziantou.Analyzer
# MA0048: File name must match type name
dotnet_diagnostic.ma0048.severity = Suggestion
# MA0051: Method is too long
dotnet_diagnostic.ma0051.severity = Suggestion
# https://www.meziantou.net/string-comparisons-are-harder-than-it-seems.htm
# MA0006 - Use String.Equals instead of equality operator
dotnet_diagnostic.ma0006.severity = Suggestion
# MA0002 - IEqualityComparer or IComparer is missing
dotnet_diagnostic.ma0002.severity = Suggestion
# MA0001 - StringComparison is missing
dotnet_diagnostic.ma0001.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token
# MA0040: Specify a cancellation token
dotnet_diagnostic.ma0032.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token
# MA0040: Flow the cancellation token when available
dotnet_diagnostic.ma0040.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable
# MA0079: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.ma0079.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable
# MA0080: Use a cancellation token using .WithCancellation()
dotnet_diagnostic.ma0080.severity = Suggestion
# Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed
dotnet_diagnostic.MA0004.severity = none
# Add regex evaluation timeout
dotnet_diagnostic.MA0009.severity = none
# Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter. Already caught by CA1305.
dotnet_diagnostic.MA0011.severity = none
# Use an overload of 'System.ArgumentException' with the parameter name. Just a suggestion since we have a bunch of justified exceptions.
dotnet_diagnostic.MA0015.severity = suggestion
# Use an explicit StringComparer to compute hash codes
dotnet_diagnostic.MA0021.severity = none
# Declare types in namespaces. Already caught by CA1050
dotnet_diagnostic.MA0047.severity = none
# Use an overload of 'GetHashCode' that has a StringComparison parameter
dotnet_diagnostic.MA0074.severity = none
# MA0049: Type name should not match containing namespace
dotnet_diagnostic.MA0049.severity = none
##################################################################################
## https://github.com/JosefPihrt/Roslynator/blob/main/docs/Configuration.md
## https://josefpihrt.github.io/docs/
## Roslynator
##################################################################################
# RCS1036 - Remove redundant empty line.
dotnet_diagnostic.rcs1036.severity = None
# RCS1037 - Remove trailing white-space.
dotnet_diagnostic.rcs1037.severity = None
# RCS1194: Implement exception constructors
dotnet_diagnostic.rcs1194.severity = None
# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait
# RCS1174: Remove redundant async/await.
dotnet_diagnostic.rcs1174.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method
# RCS1210: Return Task.FromResult instead of returning null.
dotnet_diagnostic.rcs1210.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool
# RCS1090: Call 'ConfigureAwait(false)'.
dotnet_diagnostic.rcs1090.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async
#RCS1046: Asynchronous method name should end with 'Async'.
dotnet_diagnostic.rcs1046.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async
# RCS1047: Non-asynchronous method name should not end with 'Async'.
dotnet_diagnostic.rcs1047.severity = error
# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1174.md
# RCS1174: Remove redundant async/await
dotnet_diagnostic.rcs1174.severity = Suggestion
# Combine 'Enumerable.Where' method chain. It doesn't make it more readable in all cases.
dotnet_diagnostic.RCS1112.severity = suggestion
# Inline local variable.
dotnet_diagnostic.RCS1124.severity = suggestion
# Add exception to documentation comment. Nice suggestion, but we don't want to document exceptions for internal code.
dotnet_diagnostic.RCS1140.severity = suggestion
# Missing documentation
dotnet_diagnostic.RCS1141.severity = suggestion
dotnet_diagnostic.RCS1142.severity = suggestion
# Use conditional access. Suggestion because it doesn't always improve readability
dotnet_diagnostic.RCS1146.severity = suggestion
# Enum should declare explicit values. Disabled because we're not storing them.
dotnet_diagnostic.RCS1161.severity = none
# Static member in generic type should use a type parameter. Disabled because it's not always applicable.
dotnet_diagnostic.RCS1158.severity = none
# Add region name to #endregion.
dotnet_diagnostic.RCS1189.severity = none
# Convert comment to documentation comment. Disabled because it also complains about SMELL/REFACTOR comments
dotnet_diagnostic.RCS1181.severity = none
# Use Regex instance instead of static method. Disabled because it's not always worth it.
dotnet_diagnostic.RCS1186.severity = none
# Use bit shift operator.
dotnet_diagnostic.RCS1237.severity = none
# RCS1228: Unused element in documentation comment. (Equivalent to SA1614)
dotnet_diagnostic.RCS1228.severity = suggestion
# RCS1047: Non-asynchronous method name should not end with 'Async'
#dotnet_diagnostic.RCS1047.severity = suggestion
##################################################################################
## https://github.com/semihokur/asyncfixer
## AsyncFixer01
##################################################################################
# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait
# AsyncFixer01: Unnecessary async/await usage
dotnet_diagnostic.asyncfixer01.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method
# AsyncFixer02: Long-running or blocking operations inside an async method
dotnet_diagnostic.asyncfixer02.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method
# AsyncFixer03: Fire & forget async void methods
dotnet_diagnostic.asyncfixer03.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p1/#6-not-awaited-task-inside-the-using-block
# AsyncFixer04: Fire & forget async call inside a using block
dotnet_diagnostic.asyncfixer04.severity = error
##################################################################################
## https://github.com/microsoft/vs-threading
## Microsoft.VisualStudio.Threading.Analyzers
##################################################################################
# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method
# VSTHRD103: Call async methods when in an async method
dotnet_diagnostic.vsthrd103.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method
# VSTHRD100: Avoid async void methods
dotnet_diagnostic.vsthrd100.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p1/#4-unsupported-async-delegates
# VSTHRD101: Avoid unsupported async delegates
dotnet_diagnostic.vsthrd101.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p1/#5-not-awaited-task-within-using-expression
# VSTHRD107: Await Task within using expression
dotnet_diagnostic.vsthrd107.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p1/#7-unobserved-result-of-asynchronous-method
# VSTHRD110: Observe result of async calls
dotnet_diagnostic.vsthrd110.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
# VSTHRD002: Avoid problematic synchronous waits
dotnet_diagnostic.vsthrd002.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool
# VSTHRD111: Use ConfigureAwait(bool)
dotnet_diagnostic.vsthrd111.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method
# VSTHRD114: Avoid returning a null Task
dotnet_diagnostic.vsthrd114.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async
# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.vsthrd200.severity = Suggestion
# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async
# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.vsthrd200.severity = Suggestion
# VSTHRD003 Avoid awaiting foreign Tasks
dotnet_diagnostic.VSTHRD003.severity = Suggestion
##################################################################################
## https://github.com/hvanbakel/Asyncify-CSharp
## Asyncify
##################################################################################
# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
# AsyncifyInvocation: Use Task Async
dotnet_diagnostic.asyncifyinvocation.severity = error
# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits
# AsyncifyVariable: Use Task Async
dotnet_diagnostic.asyncifyvariable.severity = error
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sh text eol=lf
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/actions/build/action.yml
================================================
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
# https://doug.sh/posts/using-composite-actions-with-github-actions/
# https://wallis.dev/blog/composite-github-actions
name: "Build"
description: "Build service"
# Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables.(so they are just string)
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
inputs:
project-path:
description: Project path
required: true
service-name:
description: Service name
required: true
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
runs:
using: "composite"
steps:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows
# https://devblogs.microsoft.com/dotnet/dotnet-loves-github-actions/
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net#caching-dependencies
- name: Cache NuGet Packages
uses: actions/cache@v4
if: success()
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-dotnet-nuget
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.x.x'
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
- name: Restore .NET Tools
shell: bash
run: dotnet tool restore
# Note: `Ubuntu` file and folder names are case sensitive, be aware about naming them in solution references. because `Windows` file and folder names as case-insensitive.
# prevent windows case-insensitive for our project with: git config core.ignorecase false; - https://stackoverflow.com/a/27139487/581476
- name: Restore NuGet packages
shell: bash
if: success()
# restore root solution
run: dotnet restore
# npm install, runs `prepare` script automatically in the initialize step
- name: Install NPM Dependencies
shell: bash
if: success()
run: npm install
- name: Format Service
shell: bash
if: ${{ success()}}
run: |
npm run ci-format
- name: Build Service
shell: bash
if: ${{ success()}}
working-directory: ${{ inputs.project-path }}
run: |
dotnet build -c Release --no-restore
================================================
FILE: .github/actions/build-test/action.yml
================================================
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
# https://doug.sh/posts/using-composite-actions-with-github-actions/
# https://wallis.dev/blog/composite-github-actions
name: "Build-Test"
description: "Build and test service"
# Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables.(so they are just string)
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
inputs:
project-path:
description: Project path
required: true
tests-path:
description: Test path
required: false
default: ''
reports-path:
description: Test report path
required: true
reports-output-path:
description: Test report output path
required: true
service-name:
description: Service name
required: true
# https://stackoverflow.com/questions/70098241/using-secrets-in-composite-actions-github
token:
description: A Github PAT
required: true
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
runs:
using: "composite"
steps:
- name: Call Composite Action build
uses: ./.github/actions/build
if: success()
id: build-step
with:
project-path: ${{ inputs.project-path }}
service-name: ${{ inputs.service-name }}
- name: Call Composite Action test
uses: ./.github/actions/test
if: ${{ success() && inputs.tests-path != ''}}
id: test-step
with:
tests-path: ${{ inputs.tests-path }}
# wildcard search for files with the ".cobertura.xml" extension in all subdirectories of the current directory
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
reports-path: ${{ github.workspace }}/**/*.cobertura.xml
reports-output-path: ${{ github.workspace }}/output/test-results
service-name: ${{ inputs.service-name }}
token: ${{ inputs.token }}
no-restore: true
================================================
FILE: .github/actions/docker-build-publish/action.yml
================================================
name: "Publish and Build Docker"
description: "Publish and Build Docker "
# Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables.(so they are just string)
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
inputs:
tag-name:
description: "Tag Name"
required: true
image-name:
description: "Image Name"
required: true
registry-username:
description: "Registry username"
required: true
registry-password:
description: "Registry password"
required: true
dockerfile-path:
description: "Dockerfile path"
required: true
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
runs:
using: "composite"
steps:
##ref: https://docs.docker.com/language/golang/configure-ci-cd/
##ref: https://event-driven.io/en/how_to_buid_and_push_docker_image_with_github_actions
- name: Login to DockerHub
uses: docker/login-action@v2
if: ${{ github.ref == 'refs/heads/main' && success() }}
with:
username: ${{ inputs.registry-username }}
password: ${{ inputs.registry-password }}
- name: Docker Tag Info
shell: bash
run:
echo "Docker tag version is:" ${{ inputs.tag-name }}
- name: Build Docker Image
if: ${{ github.ref == 'refs/heads/main' && success() }}
shell: bash
run: |
docker build -t ${{ inputs.registry-username }}/${{ inputs.image-name }}:${{ inputs.tag-name }} -f "${{ github.workspace }}/${{ inputs.dockerfile-path }}" .
- name: Publish Docker Image
if: ${{ github.ref == 'refs/heads/main' && success() }}
shell: bash
run: |
docker push ${{ inputs.registry-username }}/${{ inputs.image-name }}:${{ inputs.tag-name }}
================================================
FILE: .github/actions/test/action.yml
================================================
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
# https://doug.sh/posts/using-composite-actions-with-github-actions/
# https://wallis.dev/blog/composite-github-actions
name: "Test"
description: "Test service"
# Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables.(so they are just string)
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
inputs:
tests-path:
description: Test path
required: true
reports-path:
description: Test report path
required: true
reports-output-path:
description: Test report output path
required: true
service-name:
description: Service name
required: true
# https://stackoverflow.com/questions/70098241/using-secrets-in-composite-actions-github
token:
description: A Github PAT
required: true
no-restore:
description: No restore nuget packages, but building tests because they don't build in the build composition action
default: 'true'
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
runs:
using: "composite"
steps:
# see here https://samlearnsazure.blog/2021/01/05/code-coverage-in-github-with-net-core/
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test#filter-option-details
# https://josef.codes/dotnet-core-filter-out-specific-test-projects-when-running-dotnet-test/
# https://learn.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests?pivots=xunit
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
# https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md
# https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#filters
- name: Tests
shell: bash
id: tests-step
working-directory: ${{ inputs.tests-path }}
# https://stackoverflow.com/questions/3779701/msbuild-error-msb1008-only-one-project-can-be-specified
# https://octopus.com/blog/githubactions-running-unit-tests
# we should not do 'no-build' here, because our tests not build in build phase (build composite action) and should build here
run: |
for file in $(find . -name "*.csproj" -type f); do
echo "Testing $file"
if [ ${{ inputs.no-restore }} == 'true' ]; then
echo "run tests in no-restore mode"
dotnet test "$file" -c Release --no-restore --logger "trx;LogFileName=test-results.trx" || true
else
echo "run tests in restore nuget mode"
dotnet test "$file" -c Release --logger "trx;LogFileName=test-results.trx" || true
fi
done
# GitHub Api call permissions problem here
# https://github.com/dorny/test-reporter/issues/168
# https://octopus.com/blog/githubactions-running-unit-tests
# https://github.com/dorny/test-reporter/issues/67
# https://github.com/phoenix-actions/test-reporting/pull/21
- name: Test Results
uses: phoenix-actions/test-reporting@v10
id: test-report
if: always()
with:
name: ${{ inputs.service-name }} Test Reports
reporter: dotnet-trx
token: ${{ inputs.token }}
# only-summary: 'true'
output-to: "step-summary"
path: "**/test-results.trx"
# Set action as failed if test report contains any failed test
fail-on-error: true
## https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories
## https://github.com/dorny/test-reporter/blob/0d9714ddc7ff86918ec725a527a3a069419d301a/src/utils/github-utils.ts#L44
## artifact name to download trx test result if it is in seperated workflow with github rest call, if it is not in another workflow skip this
# artifact: "'
================================================
FILE: .github/release-drafter.yml
================================================
# https://johanneskonings.dev/github/2021/02/28/github_automatic_releases_and-changelog/
# https://tiagomichaelsousa.dev/articles/stop-writing-your-changelogs-manually
# https://github.com/release-drafter/release-drafter/issues/551
# https://github.com/release-drafter/release-drafter/pull/1013
# https://github.com/release-drafter/release-drafter/issues/139
# https://github.com/atk4/data/blob/develop/.github/release-drafter.yml
# This release drafter follows the conventions from https://keepachangelog.com, https://common-changelog.org/
# https://www.conventionalcommits.org
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
template: |
## What Changed 👀
$CHANGES
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
categories:
- title: 🚀 Features
labels:
- feature
- title: 🐛 Bug Fixes
labels:
- fix
- bug
- title: 🧪 Test
labels:
- test
- title: 👷 CI
labels:
- ci
- title: ♻️ Refactor
labels:
- changed
- enhancement
- refactor
- title: ⛔️ Deprecated
labels:
- deprecated
- title: 🔐 Security
labels:
- security
- title: 📄 Documentation
labels:
- docs
- documentation
- title: 🧩 Dependency Updates
labels:
- deps
- dependencies
- title: 🧰 Maintenance
label: 'chore'
- title: 📝 Other changes
## putting no labels pr to `Other Changes` category with no label - https://github.com/release-drafter/release-drafter/issues/139#issuecomment-480473934
# https://www.trywilco.com/post/wilco-ci-cd-github-heroku
# https://github.com/release-drafter/release-drafter#autolabeler
# https://github.com/fuxingloh/multi-labeler
# Using regex for defining rules - https://regexr.com/ - https://regex101.com/
autolabeler:
- label: 'chore'
branch:
- '/(chore)\/.*/'
- label: 'security'
branch:
- '/(security)\/.*/'
- label: 'refactor'
branch:
- '/(refactor)\/.*/'
- label: 'docs'
branch:
- '/(docs)\/.*/'
- label: 'ci'
branch:
- '/(ci)\/.*/'
- label: 'test'
branch:
- '/(test)\/.*/'
- label: 'bug'
branch:
- '/(fix)\/.*/'
- label: 'feature'
branch:
- '/(feat)\/.*/'
- label: 'minor'
branch:
- '/(feat)\/.*/'
- label: 'patch'
branch:
- '/(fix)\/.*/'
body:
- '/JIRA-[0-9]{1,4}/'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- major
minor:
labels:
- minor
patch:
labels:
- patch
default: patch
exclude-labels:
- skip-changelog
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [ "main"]
paths-ignore:
- "README.md"
pull_request:
branches: [ "main"]
paths-ignore:
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and Test Flight Microservice
uses: ./.github/actions/build-test
if: success()
id: build-test-flight-step
with:
project-path: 'src/Services/Flight/src/Flight.Api'
tests-path: 'src/Services/Flight/tests/'
# wildcard search for files with the ".cobertura.xml" extension in all subdirectories of the current directory
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
reports-path: ${{ github.workspace }}/**/*.cobertura.xml
reports-output-path: ${{ github.workspace }}/output/test-results
service-name: 'Flight'
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Test Identity Microservice
uses: ./.github/actions/build-test
if: success()
id: build-test-identity-step
with:
project-path: 'src/Services/Identity/src/Identity.Api'
tests-path: 'src/Services/Identity/tests/'
# wildcard search for files with the ".cobertura.xml" extension in all subdirectories of the current directory
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
reports-path: ${{ github.workspace }}/**/*.cobertura.xml
reports-output-path: ${{ github.workspace }}/output/test-results
service-name: 'Identity'
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Test Passenger Microservice
uses: ./.github/actions/build-test
if: success()
id: build-test-passenger-step
with:
project-path: 'src/Services/Passenger/src/Passenger.Api'
tests-path: 'src/Services/Passenger/tests/'
# wildcard search for files with the ".cobertura.xml" extension in all subdirectories of the current directory
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
reports-path: ${{ github.workspace }}/**/*.cobertura.xml
reports-output-path: ${{ github.workspace }}/output/test-results
service-name: 'Passenger'
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Test Booking Microservice
uses: ./.github/actions/build-test
if: success()
id: build-test-booking-step
with:
project-path: 'src/Services/Booking/src/Booking.Api'
tests-path: 'src/Services/Booking/tests/'
# wildcard search for files with the ".cobertura.xml" extension in all subdirectories of the current directory
# https://www.jamescroft.co.uk/combining-multiple-code-coverage-results-in-azure-devops/
# https://stackoverflow.com/questions/53255065/dotnet-unit-test-with-coverlet-how-to-get-coverage-for-entire-solution-and-not
reports-path: ${{ github.workspace }}/**/*.cobertura.xml
reports-output-path: ${{ github.workspace }}/output/test-results
service-name: 'Booking'
token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Release Drafter
if: ${{ github.ref == 'refs/heads/main' && success() }}
id: last_release
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release Version Info
run:
echo "Release version is:" ${{ steps.last_release.outputs.tag_name }}
- name: Build and Publish Identity Microservice to Docker
if: ${{ github.ref == 'refs/heads/main' && success() }}
uses: ./.github/actions/docker-build-publish
with:
tag-name: ${{ steps.last_release.outputs.tag_name }}
registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
registry-password: ${{ secrets.DOCKERHUB_PASSWORD }}
dockerfile-path: 'src/Services/Identity/Dockerfile'
image-name: 'booking-microservices-identity'
- name: Build and Publish Flight Microservice to Docker
if: ${{ github.ref == 'refs/heads/main' && success() }}
uses: ./.github/actions/docker-build-publish
with:
tag-name: ${{ steps.last_release.outputs.tag_name }}
registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
registry-password: ${{ secrets.DOCKERHUB_PASSWORD }}
dockerfile-path: 'src/Services/Flight/Dockerfile'
image-name: 'booking-microservices-flight'
- name: Build and Publish Passenger Microservice to Docker
if: ${{ github.ref == 'refs/heads/main' && success() }}
uses: ./.github/actions/docker-build-publish
with:
tag-name: ${{ steps.last_release.outputs.tag_name }}
registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
registry-password: ${{ secrets.DOCKERHUB_PASSWORD }}
dockerfile-path: 'src/Services/Passenger/Dockerfile'
image-name: 'booking-microservices-passenger'
- name: Build and Publish Booking Microservice to Docker
if: ${{ github.ref == 'refs/heads/main' && success() }}
uses: ./.github/actions/docker-build-publish
with:
tag-name: ${{ steps.last_release.outputs.tag_name }}
registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
registry-password: ${{ secrets.DOCKERHUB_PASSWORD }}
dockerfile-path: 'src/Services/Booking/Dockerfile'
image-name: 'booking-microservices-booking'
================================================
FILE: .github/workflows/release-drafter-labeler.yml
================================================
name: Release Drafter Auto Labeler
on:
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
jobs:
auto-labeler:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter.yml
disable-releaser: true # only run auto-labeler for PRs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# JetBrains Rider
.idea/
*.sln.iml
# Tye
.tye/
*.jwk
# Monitoring
**/grafana-data
# EventStore
**/eventstore
================================================
FILE: .husky/commit-msg
================================================
npx --no -- commitlint --edit ${1}
================================================
FILE: .husky/pre-commit
================================================
npm run format
npm run ci-format
================================================
FILE: CONTRIBUTION.md
================================================
## Contribution
This is great that you'd like to contribute to this project. All change requests should go through the steps described below.
## Pull Requests
**Please, make sure you open an issue before starting with a Pull Request, unless it's a typo or a really obvious error.** Pull requests are the best way to propose changes.
## Conventional commits
Our repository follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification. Releasing to GitHub and NuGet is done with the support of [semantic-release](https://semantic-release.gitbook.io/semantic-release/).
Pull requests should have a title that follows the specification, otherwise, merging is blocked. If you are not familiar with the specification simply ask maintainers to modify. You can also use this cheatsheet if you want:
- `fix: ` prefix in the title indicates that PR is a bug fix and PATCH release must be triggered.
- `feat: ` prefix in the title indicates that PR is a feature and MINOR release must be triggered.
- `docs: ` prefix in the title indicates that PR is only related to the documentation and there is no need to trigger release.
- `chore: ` prefix in the title indicates that PR is only related to cleanup in the project and there is no need to trigger release.
- `test: ` prefix in the title indicates that PR is only related to tests and there is no need to trigger release.
- `refactor: ` prefix in the title indicates that PR is only related to refactoring and there is no need to trigger release.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
================================================
FILE: Directory.Build.props
================================================
net10.0
enable
enable
$(MSBuildThisFileDirectory)
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
runtime; build; native; contentfiles; analyzers; buildtransitive
true
true
false
latest-Recommended
Recommended
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Meysam Hadeli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
> 🚀 **A practical microservices with the latest technologies and architectures like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit, and Aspire in .Net 10.**
## You can find other version of this project here:
- [Booking with Modular Monolith Architecture](https://github.com/meysamhadeli/booking-modular-monolith)
- [Booking with Monolith Architecture](https://github.com/meysamhadeli/booking-monolith)
# Table of Contents
- [The Goals of This Project](#the-goals-of-this-project)
- [Technologies - Libraries](#technologies---libraries)
- [Key Features](#key-features)
- [When to Use](#when-to-use)
- [Challenges](#challenges)
- [The Domain and Bounded Context - Service Boundary](#the-domain-and-bounded-context---service-boundary)
- [Structure of Project](#structure-of-project)
- [Development Setup](#development-setup)
- [Dotnet Tools Packages](#dotnet-tools-packages)
- [Husky](#husky)
- [Upgrade Nuget Packages](#upgrade-nuget-packages)
- [How to Run](#how-to-run)
- [Config Certificate](#config-certificate)
- [Aspire](#aspire)
- [Docker Compose](#docker-compose)
- [Kubernetes](#kubernetes)
- [Build](#build)
- [Run](#run)
- [Test](#test)
- [Documentation Apis](#documentation-apis)
- [Support](#support)
- [Contribution](#contribution)
## The Goals of This Project
- :sparkle: Using `Vertical Slice Architecture` for `architecture` level.
- :sparkle: Using `Domain Driven Design (DDD)` to implement all `business logic`.
- :sparkle: Using `Rabbitmq` on top of `Masstransit` for `Event Driven Architecture`.
- :sparkle: Using `gRPC` for `internal communication`.
- :sparkle: Using `CQRS` implementation with `MediatR` library.
- :sparkle: Using `Postgres` for `write side` database.
- :sparkle: Using `MongoDB` for `read side` database.
- :sparkle: Using `Event Store` for `write side` of Booking Microservice/Module to store all `historical change` of aggregate.
- :sparkle: Using `Inbox Pattern` for ensuring message idempotency for receiver and `Exactly once Delivery`.
- :sparkle: Using `Outbox Pattern` for ensuring no message is lost and there is at `At Least One Delivery`.
- :sparkle: Using `Unit Testing` for testing small units and mocking our dependencies with `Nsubstitute`.
- :sparkle: Using `End-To-End Testing` and `Integration Testing` for testing `features` with all dependencies using `testcontainers`.
- :sparkle: Using `Fluent Validation` and a `Validation Pipeline Behaviour` on top of `MediatR`.
- :sparkle: Using `Minimal API` for all endpoints.
- :sparkle: Using `AspNetCore OpenApi` for `generating` built-in support `OpenAPI documentation` in ASP.NET Core.
- :sparkle: Using `Health Check` for `reporting` the `health` of app infrastructure components.
- :sparkle: Using `Docker-Compose` and `Kubernetes` for our deployment mechanism.
- :sparkle: Using `Kibana` on top of `Serilog` for `logging`.
- :sparkle: Using `OpenTelemetry` for distributed tracing on top of `Jaeger`.
- :sparkle: Using `OpenTelemetry` for monitoring on top of `Prometheus` and `Grafana`.
- :sparkle: Using `IdentityServer` for authentication and authorization base on `OpenID-Connect` and `OAuth2`.
- :sparkle: Using `Yarp` as a microservices `gateway`.
- :sparkle: Using `Kubernetes` to achieve efficient `scaling` and ensure `high availability` for each of our microservices.
- :sparkle: Using `Nginx Ingress Controller` for `load balancing` between our microservices top of `Kubernetes`.
- :sparkle: Using `cert-manager` to Configure `TLS` in `kubernetes cluster`.
- :sparkle: Using `Aspire` for `service discovery`, `observability`, and `local orchestration` of microservices.
## Technologies - Libraries
- ✔️ **[`.NET 10`](https://github.com/dotnet/aspnetcore)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core.
- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core.
- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
- ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi)** - Provides built-in support for OpenAPI document generation in ASP.NET Core.
- ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET.
- ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .NET.
- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules.
- ✔️ **[`Scalar`](https://github.com/scalar/scalar/tree/main/packages/scalar.aspnetcore)** - Scalar provides an easy way to render beautiful API references based on OpenAPI/Swagger documents.
- ✔️ **[`Swagger UI`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)** - Swagger tools for documenting API's built on ASP.NET Core.
- ✔️ **[`Serilog`](https://github.com/serilog/serilog)** - Simple .NET logging with fully-structured events
- ✔️ **[`Polly`](https://github.com/App-vNext/Polly)** - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
- ✔️ **[`Scrutor`](https://github.com/khellang/Scrutor)** - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
- ✔️ **[`Opentelemetry-dotnet`](https://github.com/open-telemetry/opentelemetry-dotnet)** - The OpenTelemetry .NET Client
- ✔️ **[`DuendeSoftware IdentityServer`](https://github.com/DuendeSoftware/IdentityServer)** - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core.
- ✔️ **[`EasyCaching`](https://github.com/dotnetcore/EasyCaching)** - Open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier.
- ✔️ **[`Mapster`](https://github.com/MapsterMapper/Mapster)** - Convention-based object-object mapper in .NET.
- ✔️ **[`Hellang.Middleware.ProblemDetails`](https://github.com/khellang/Middleware/tree/master/src/ProblemDetails)** - A middleware for handling exception in .Net Core.
- ✔️ **[`NewId`](https://github.com/phatboyg/NewId)** - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs.
- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET.
- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier.
- ✔️ **[`gRPC-dotnet`](https://github.com/grpc/grpc-dotnet)** - gRPC functionality for .NET.
- ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing.
- ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB.
- ✔️ **[`xUnit.net`](https://github.com/xunit/xunit)** - A free, open source, community-focused unit testing tool for the .NET Framework.
- ✔️ **[`Respawn`](https://github.com/jbogard/Respawn)** - Respawn is a small utility to help in resetting test databases to a clean state.
- ✔️ **[`Testcontainers`](https://github.com/testcontainers/testcontainers-dotnet)** - Testcontainers for .NET is a library to support tests with throwaway instances of Docker containers.
- ✔️ **[`K6`](https://github.com/grafana/k6)** - Modern load testing for developers and testers in the DevOps era.
- ✔️ **[`Aspire`](https://github.com/dotnet/aspire)** - .NET stack for building and orchestrating observable, distributed cloud-native applications.
## Key Features
1. **Independent Services**: Each service is a separate project with its own database and deployment pipeline, enabling independent development and deployment.
2. **Decentralized Communication**: Services communicate via APIs (REST, gRPC) or message brokers (RabbitMQ, Kafka), ensuring loose coupling and resilience.
3. **Scalability**: Services can be scaled independently based on demand, allowing efficient resource utilization.
4. **Fault Tolerance**: Failures are isolated, preventing cascading failures and ensuring high availability.
5. **Technology Agnostic**: Services can use different technologies, frameworks, or databases, providing flexibility.
## When to Use
1. **Large and Complex Projects**: Ideal for applications with complex business logic that can be broken into smaller, manageable services.
2. **High Scalability Needs**: Suitable for applications requiring independent scaling of components.
3. **Fault Tolerance and High Availability**: Perfect for systems where failure isolation and uptime are critical.
4. **Distributed Teams**: Enables teams to work independently on different services.
5. **Frequent Updates**: Supports continuous deployment and A/B testing for individual services.
6. **Technology Diversity**: Allows the use of different technologies for different services.
## Challenges
- Increased complexity in management, DevOps overhead, data consistency, latency, and higher costs.
## The Domain And Bounded Context - Service Boundary
- `Identity Service`: The Identity Service is a bounded context for the authentication and authorization of users using [Identity Server](https://github.com/DuendeSoftware/IdentityServer). This service is responsible for creating new users and their corresponding roles and permissions using [.Net Core Identity](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) and Jwt authentication and authorization.
- `Flight Service`: The Flight Service is a bounded context `CRUD` service to handle flight related operations.
- `Passenger Service`: The Passenger Service is a bounded context for managing passenger information, tracking activities and subscribing to get notification for out of stock products.
- `Booking Service`: The Booking Service is a bounded context for managing all operation related to booking ticket.

## Structure of Project
In this project, I used [vertical slice architecture](https://jimmybogard.com/vertical-slice-architecture/) at the architectural level and [feature folder structure](http://www.kamilgrzybek.com/design/feature-folders/) to structure my files.
I treat each request as a distinct use case or slice, encapsulating and grouping all concerns from front-end to back.
When adding or changing a feature in an application in n-tire architecture, we are typically touching many "layers" in an application. We are changing the user interface, adding fields to models, modifying validation, and so on. Instead of coupling across a layer, we couple vertically along a slice. We `minimize coupling` `between slices`, and `maximize coupling` `in a slice`.
With this approach, each of our vertical slices can decide for itself how to best fulfill the request. New features only add code, we're not changing shared code and worrying about side effects.
Instead of grouping related action methods in one controller, as found in traditional ASP.net controllers, I used the [REPR pattern](https://deviq.com/design-patterns/repr-design-pattern). Each action gets its own small endpoint, consisting of a route, the action, and an `IMediator` instance (see [MediatR](https://github.com/jbogard/MediatR)). The request is passed to the `IMediator` instance, routed through a [`Mediatr pipeline`](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/) where custom [middleware](https://github.com/jbogard/MediatR/wiki/Behaviors) can log, validate and intercept requests. The request is then handled by a request specific `IRequestHandler` which performs business logic before returning the result.
The use of the [mediator pattern](https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/) in my controllers creates clean and [thin controllers](https://codeopinion.com/thin-controllers-cqrs-mediatr/). By separating action logic into individual handlers we support the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) and [Don't Repeat Yourself principles](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), this is because traditional controllers tend to become bloated with large action methods and several injected `Services` only being used by a few methods.
I used CQRS to decompose my features into small parts that makes our application:
- Maximize performance, scalability and simplicity.
- Easy to maintain and add features to. Changes only affect one command or query, avoiding breaking changes or creating side effects.
- It gives us better separation of concerns and cross-cutting concern (with help of mediatr behavior pipelines), instead of bloated service classes doing many things.
Using the CQRS pattern, we cut each business functionality into vertical slices, for each of these slices we group classes (see [technical folders structure](http://www.kamilgrzybek.com/design/feature-folders)) specific to that feature together (command, handlers, infrastructure, repository, controllers, etc). In our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods.
## Development Setup
### Dotnet Tools Packages
For installing our requirement packages with .NET cli tools, we need to install `dotnet tool manifest`.
```bash
dotnet new tool-manifest
```
And after that we can restore our dotnet tools packages with .NET cli tools from `.config` folder and `dotnet-tools.json` file.
```
dotnet tool restore
```
### Husky
Here we use `husky` to handel some pre commit rules and we used `conventional commits` rules and `formatting` as pre commit rules, here in [package.json](.././package.json). of course, we can add more rules for pre commit in future. (find more about husky in the [documentation](https://typicode.github.io/husky/get-started.html))
We need to install `husky` package for `manage` `pre commits hooks` and also I add two packages `@commitlint/cli` and `@commitlint/config-conventional` for handling conventional commits rules in [package.json](.././package.json).
Run the command bellow in the root of project to install all npm dependencies related to husky:
```bash
npm install
```
> Note: In the root of project we have `.husky` folder and it has `commit-msg` file for handling conventional commits rules with provide user friendly message and `pre-commit` file that we can run our `scripts` as a `pre-commit` hooks. that here we call `format` script from [package.json](./package.json) for formatting purpose.
### Upgrade Nuget Packages
For upgrading our nuget packages to last version, we use the great package [dotnet-outdated](https://github.com/dotnet-outdated/dotnet-outdated).
Run the command below in the root of project to upgrade all of packages to last version:
```bash
dotnet outdated -u
```
## How to Run
> ### Config Certificate
Run the following commands to [Config SSL](https://docs.microsoft.com/en-us/aspnet/core/security/docker-compose-https?view=aspnetcore-6.0) in your system:
#### Windows using Linux containers
```bash
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p password
dotnet dev-certs https --trust
```
> Note: for running this command in `powershell` use `$env:USERPROFILE` instead of `%USERPROFILE%`*
#### macOS or Linux
```bash
dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p $CREDENTIAL_PLACEHOLDER$
dotnet dev-certs https --trust
```
### Aspire
To run the application using the `Aspire App Host`, execute the following command from the solution root:
```bash
aspire run
```
> Note:The `Aspire dashboard` will be available at `http://localhost:18888`
> ### Docker Compose
To run this app in `Docker`, use the [docker-compose.yaml](./deployments/docker-compose/docker-compose.yaml) and execute the below command at the `root` of the application:
```bash
docker-compose -f ./deployments/docker-compose/docker-compose.yaml up -d
```
> ### Kubernetes
To `configure TLS` in the `Kubernetes cluster`, we need to install `cert-manager` based on the [docs](https://cert-manager.io/docs/installation) and run the following commands to apply TLS in our application. Here, we use [Let's Encrypt](https://letsencrypt.org/) to encrypt our certificate.
```bash
kubectl apply -f ./deployments/kubernetes/booking-cert-manager.yml
```
To apply all necessary `deployments`, `pods`, `services`, `ingress`, and `config maps`, please run the following command:
```bash
kubectl apply -f ./deployments/kubernetes/booking-microservices.yml
```
> ### Build
To `build` all microservices, run this command in the `root` of the project:
```bash
dotnet build
```
> ### Run
To `run` each microservice, run this command in the root of the `Api` folder of each microservice where the `csproj` file is located:
```bash
dotnet run
```
> ### Test
To `test` all microservices, run this command in the `root` of the project:
```bash
dotnet test
```
> ### Documentation Apis
Each microservice provides `API documentation` and navigate to `/swagger` for `Swagger OpenAPI` or `/scalar/v1` for `Scalar OpenAPI` to visit list of endpoints.
As part of API testing, I created the [booking.rest](./booking.rest) file which can be run with the [REST Client](https://github.com/Huachao/vscode-restclient) `VSCode plugin`.
# Support
If you like my work, feel free to:
- ⭐ this repository. And we will be happy together :)
Thanks a bunch for supporting me!
## Contribution
Thanks to all [contributors](https://github.com/meysamhadeli/booking-microservices/graphs/contributors), you're awesome and this wouldn't be possible without you! The goal is to build a categorized, community-driven collection of very well-known resources.
Please follow this [contribution guideline](./CONTRIBUTION.md) to submit a pull request or create the issue.
## Project References & Credits
- [https://github.com/jbogard/ContosoUniversityDotNetCore-Pages](https://github.com/jbogard/ContosoUniversityDotNetCore-Pages)
- [https://github.com/kgrzybek/modular-monolith-with-ddd](https://github.com/kgrzybek/modular-monolith-with-ddd)
- [https://github.com/oskardudycz/EventSourcing.NetCore](https://github.com/oskardudycz/EventSourcing.NetCore)
- [https://github.com/thangchung/clean-architecture-dotnet](https://github.com/thangchung/clean-architecture-dotnet)
- [https://github.com/pdevito3/MessageBusTestingInMemHarness](https://github.com/pdevito3/MessageBusTestingInMemHarness)
## License
This project is made available under the MIT license. See [LICENSE](https://github.com/meysamhadeli/booking-microservices/blob/main/LICENSE) for details.
================================================
FILE: assets/booking-microservices.drawio
================================================
================================================
FILE: assets/vertical-slice-architecture.excalidraw
================================================
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"type": "rectangle",
"version": 242,
"versionNonce": 1509780320,
"isDeleted": false,
"id": "80OGzNPG6Gk8NAvbV3XaF",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 648,
"y": 187,
"strokeColor": "#000000",
"backgroundColor": "#a8bffe",
"width": 538,
"height": 62,
"seed": 246982778,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "46GLDhDwmnc8RGy3v8OK8"
}
],
"updated": 1679316672934,
"link": null,
"locked": false
},
{
"type": "text",
"version": 137,
"versionNonce": 703919968,
"isDeleted": false,
"id": "46GLDhDwmnc8RGy3v8OK8",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 897.848014831543,
"y": 201.2,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 38.30397033691406,
"height": 33.6,
"seed": 2080176422,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949309,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Api",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "80OGzNPG6Gk8NAvbV3XaF",
"originalText": "Api"
},
{
"type": "rectangle",
"version": 358,
"versionNonce": 356515488,
"isDeleted": false,
"id": "nZuYK7wbLObwRvpRRLHay",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 648,
"y": 263,
"strokeColor": "#000000",
"backgroundColor": "#fea8d5",
"width": 538,
"height": 62,
"seed": 287502970,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "OALII-DXtatRPgn_EkHfp"
}
],
"updated": 1679316735759,
"link": null,
"locked": false
},
{
"type": "text",
"version": 246,
"versionNonce": 1126108000,
"isDeleted": false,
"id": "OALII-DXtatRPgn_EkHfp",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 845.628044128418,
"y": 277.2,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 142.74391174316406,
"height": 33.6,
"seed": 1016531494,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949309,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Application",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "nZuYK7wbLObwRvpRRLHay",
"originalText": "Application"
},
{
"type": "rectangle",
"version": 282,
"versionNonce": 787808928,
"isDeleted": false,
"id": "za_4vz64MSfPF5TWmD7wj",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 650,
"y": 338,
"strokeColor": "#000000",
"backgroundColor": "#f30358",
"width": 538,
"height": 62,
"seed": 676018342,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "6CqYCSOKHqhqJ8nf4b-Sv"
}
],
"updated": 1679316783390,
"link": null,
"locked": false
},
{
"type": "text",
"version": 189,
"versionNonce": 1441177440,
"isDeleted": false,
"id": "6CqYCSOKHqhqJ8nf4b-Sv",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 816.618049621582,
"y": 352.2,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 204.76390075683594,
"height": 33.6,
"seed": 1067355322,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949309,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Infrastructure",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "za_4vz64MSfPF5TWmD7wj",
"originalText": "Infrastructure"
},
{
"type": "rectangle",
"version": 326,
"versionNonce": 1669046112,
"isDeleted": false,
"id": "t2sZwLLvmq3y2ndIbEomB",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 70,
"angle": 0,
"x": 648,
"y": 413,
"strokeColor": "#000000",
"backgroundColor": "#9d9ca2",
"width": 538,
"height": 62,
"seed": 1173221990,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "b3wdaWjaVmgHpzMD26uKD"
}
],
"updated": 1679316844215,
"link": null,
"locked": false
},
{
"type": "text",
"version": 224,
"versionNonce": 1385935712,
"isDeleted": false,
"id": "b3wdaWjaVmgHpzMD26uKD",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 70,
"angle": 0,
"x": 886.5500106811523,
"y": 427.2,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 60.89997863769531,
"height": 33.6,
"seed": 1307397882,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949310,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Core",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "t2sZwLLvmq3y2ndIbEomB",
"originalText": "Core"
},
{
"type": "rectangle",
"version": 202,
"versionNonce": 1461187232,
"isDeleted": false,
"id": "FQZImjU2-VUOATU9Yeyly",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 678,
"y": 154,
"strokeColor": "#000000",
"backgroundColor": "#fefda8",
"width": 48,
"height": 361,
"seed": 1254939642,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [],
"updated": 1679316609154,
"link": null,
"locked": false
},
{
"type": "rectangle",
"version": 249,
"versionNonce": 1540775776,
"isDeleted": false,
"id": "_Vw9EnXAyzxRDEzXCTfeL",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 742,
"y": 153.5,
"strokeColor": "#000000",
"backgroundColor": "#fefda8",
"width": 48,
"height": 361,
"seed": 523058342,
"groupIds": [],
"roundness": {
"type": 3
},
"boundElements": [],
"updated": 1679316594766,
"link": null,
"locked": false
},
{
"type": "text",
"version": 249,
"versionNonce": 871687840,
"isDeleted": false,
"id": "hyJiOwPt7LFndn5R0xgfL",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 4.707547804955119,
"x": 637.1248451774691,
"y": 317.9455509364301,
"strokeColor": "#000000",
"backgroundColor": "#f9e79f",
"width": 130.73194885253906,
"height": 33.6,
"seed": 678740006,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315961675,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Feature 1",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Feature 1"
},
{
"type": "text",
"version": 182,
"versionNonce": 1494113120,
"isDeleted": false,
"id": "7KOHd5JA_wVMmwXPVT1N3",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 80,
"angle": 4.7123889803846915,
"x": 695.6880416870117,
"y": 313.20000000000005,
"strokeColor": "#000000",
"backgroundColor": "#f9e79f",
"width": 143.07994079589844,
"height": 33.6,
"seed": 1387191482,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949310,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Feature 2",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Feature 2"
},
{
"type": "text",
"version": 163,
"versionNonce": 1243581088,
"isDeleted": false,
"id": "SuFNrbzZGowiIybusnadN",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 70,
"angle": 0,
"x": 748,
"y": 96,
"strokeColor": "#000000",
"backgroundColor": "#f9e79f",
"width": 360.47186279296875,
"height": 33.6,
"seed": 2006173690,
"groupIds": [],
"roundness": null,
"boundElements": [],
"updated": 1679315949310,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "Vertical Slice Architecture",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Vertical Slice Architecture"
}
],
"appState": {
"gridSize": null,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}
================================================
FILE: booking-microservices.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CD4A4407-C3B0-422D-BB8C-2A810CED9938}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGateway", "ApiGateway", "{CDFA86FA-BBBA-4A5B-A833-3BE219E373E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{B19FD14B-4DFE-26B6-646B-3D5D94CC4D36}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{C734CEF7-A2AC-3076-84D8-694B7490AA9D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Flight", "Flight", "{A3579DE0-F7C5-67E8-3CF8-3AC89B64E059}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Booking", "Booking", "{C6034A5C-F49A-5FA4-86A6-65B2CB19613F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Passenger", "Passenger", "{9D4F3958-FE6E-C048-E6F9-6F53D8AF03CA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{B465D535-05D9-3A0A-08BF-35A1C18CEC46}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A2834164-BF04-BF13-ADC5-A97145852861}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1B4FBE3A-43F5-1B1E-2877-3036AC5431EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DDEDC5E0-5D13-A45C-2393-A774DD4A1A07}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{51D8F471-B8EB-AD1C-0E89-AA84C5D0C759}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{773BFBD8-04CD-79F8-8301-C81308C3ED45}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{4B043475-1AFA-C467-FE09-A46D09CD6936}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CED3889-AECF-A6CD-55DC-F680D3C18861}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{54BCCDE8-25E6-6FCB-4A9E-D5D2AF76D352}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Booking.Api", "src\Services\Booking\src\Booking.Api\Booking.Api.csproj", "{D3BF565A-C413-4185-9528-BE1B4F46993C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Booking", "src\Services\Booking\src\Booking\Booking.csproj", "{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flight", "src\Services\Flight\src\Flight\Flight.csproj", "{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flight.Api", "src\Services\Flight\src\Flight.Api\Flight.Api.csproj", "{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity", "src\Services\Identity\src\Identity\Identity.csproj", "{BCDEAB10-6373-46E7-B408-846A3B0B508B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Api", "src\Services\Identity\src\Identity.Api\Identity.Api.csproj", "{B0EC74C5-9B2D-492C-ABAE-3E868397B122}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Passenger", "src\Services\Passenger\src\Passenger\Passenger.csproj", "{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Passenger.Api", "src\Services\Passenger\src\Passenger.Api\Passenger.Api.csproj", "{101FFD12-17A4-4615-9438-F347BBF4CC85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingBlocks", "src\BuildingBlocks\BuildingBlocks.csproj", "{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{427BE8BE-DA7B-FC74-412B-547671E05463}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiGateway", "src\ApiGateway\src\ApiGateway.csproj", "{C015BF35-6977-407B-8948-636A9C81C5BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Booking\tests\IntegrationTest\Integration.Test.csproj", "{19A89F36-FD3A-448D-90D1-04A1B67BB255}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EndToEnd.Test", "src\Services\Flight\tests\EndToEndTest\EndToEnd.Test.csproj", "{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Flight\tests\IntegrationTest\Integration.Test.csproj", "{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unit.Test", "src\Services\Flight\tests\UnitTest\Unit.Test.csproj", "{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Identity\tests\IntegrationTest\Integration.Test.csproj", "{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Test", "src\Services\Passenger\tests\IntegrationTest\Integration.Test.csproj", "{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C4287034-6833-4505-A6EB-704A86392ECB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "src\Aspire\src\AppHost\AppHost.csproj", "{490BCB11-314C-473C-9B85-A32164783507}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "src\Aspire\src\ServiceDefaults\ServiceDefaults.csproj", "{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|x64.ActiveCfg = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|x64.Build.0 = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|x86.ActiveCfg = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Debug|x86.Build.0 = Debug|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|Any CPU.Build.0 = Release|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|x64.ActiveCfg = Release|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|x64.Build.0 = Release|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|x86.ActiveCfg = Release|Any CPU
{D3BF565A-C413-4185-9528-BE1B4F46993C}.Release|x86.Build.0 = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|x64.ActiveCfg = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|x64.Build.0 = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|x86.ActiveCfg = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Debug|x86.Build.0 = Debug|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|Any CPU.Build.0 = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|x64.ActiveCfg = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|x64.Build.0 = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|x86.ActiveCfg = Release|Any CPU
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8}.Release|x86.Build.0 = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|x64.Build.0 = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|x86.ActiveCfg = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Debug|x86.Build.0 = Debug|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|Any CPU.Build.0 = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|x64.ActiveCfg = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|x64.Build.0 = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|x86.ActiveCfg = Release|Any CPU
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279}.Release|x86.Build.0 = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|x64.ActiveCfg = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|x64.Build.0 = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|x86.ActiveCfg = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Debug|x86.Build.0 = Debug|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|Any CPU.Build.0 = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|x64.ActiveCfg = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|x64.Build.0 = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|x86.ActiveCfg = Release|Any CPU
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2}.Release|x86.Build.0 = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|x64.ActiveCfg = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|x64.Build.0 = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|x86.ActiveCfg = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Debug|x86.Build.0 = Debug|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|Any CPU.Build.0 = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|x64.ActiveCfg = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|x64.Build.0 = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|x86.ActiveCfg = Release|Any CPU
{BCDEAB10-6373-46E7-B408-846A3B0B508B}.Release|x86.Build.0 = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|x64.ActiveCfg = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|x64.Build.0 = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|x86.ActiveCfg = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Debug|x86.Build.0 = Debug|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|Any CPU.Build.0 = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|x64.ActiveCfg = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|x64.Build.0 = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|x86.ActiveCfg = Release|Any CPU
{B0EC74C5-9B2D-492C-ABAE-3E868397B122}.Release|x86.Build.0 = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|x64.Build.0 = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Debug|x86.Build.0 = Debug|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|x64.ActiveCfg = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|x64.Build.0 = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|x86.ActiveCfg = Release|Any CPU
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538}.Release|x86.Build.0 = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|x64.ActiveCfg = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|x64.Build.0 = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|x86.ActiveCfg = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Debug|x86.Build.0 = Debug|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|Any CPU.Build.0 = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|x64.ActiveCfg = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|x64.Build.0 = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|x86.ActiveCfg = Release|Any CPU
{101FFD12-17A4-4615-9438-F347BBF4CC85}.Release|x86.Build.0 = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|x64.ActiveCfg = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|x64.Build.0 = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|x86.ActiveCfg = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Debug|x86.Build.0 = Debug|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|Any CPU.Build.0 = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|x64.ActiveCfg = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|x64.Build.0 = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|x86.ActiveCfg = Release|Any CPU
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2}.Release|x86.Build.0 = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|x64.ActiveCfg = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|x64.Build.0 = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|x86.ActiveCfg = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Debug|x86.Build.0 = Debug|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|Any CPU.Build.0 = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|x64.ActiveCfg = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|x64.Build.0 = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|x86.ActiveCfg = Release|Any CPU
{C015BF35-6977-407B-8948-636A9C81C5BE}.Release|x86.Build.0 = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|x64.ActiveCfg = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|x64.Build.0 = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|x86.ActiveCfg = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Debug|x86.Build.0 = Debug|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|Any CPU.Build.0 = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|x64.ActiveCfg = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|x64.Build.0 = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|x86.ActiveCfg = Release|Any CPU
{19A89F36-FD3A-448D-90D1-04A1B67BB255}.Release|x86.Build.0 = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|x64.ActiveCfg = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|x64.Build.0 = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|x86.ActiveCfg = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Debug|x86.Build.0 = Debug|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|Any CPU.Build.0 = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|x64.ActiveCfg = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|x64.Build.0 = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|x86.ActiveCfg = Release|Any CPU
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4}.Release|x86.Build.0 = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|x64.ActiveCfg = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|x64.Build.0 = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|x86.ActiveCfg = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Debug|x86.Build.0 = Debug|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|Any CPU.Build.0 = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|x64.ActiveCfg = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|x64.Build.0 = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|x86.ActiveCfg = Release|Any CPU
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B}.Release|x86.Build.0 = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|x64.ActiveCfg = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|x64.Build.0 = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|x86.ActiveCfg = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Debug|x86.Build.0 = Debug|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|Any CPU.Build.0 = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|x64.ActiveCfg = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|x64.Build.0 = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|x86.ActiveCfg = Release|Any CPU
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB}.Release|x86.Build.0 = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|x64.ActiveCfg = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|x64.Build.0 = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|x86.ActiveCfg = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Debug|x86.Build.0 = Debug|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|Any CPU.Build.0 = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|x64.ActiveCfg = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|x64.Build.0 = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|x86.ActiveCfg = Release|Any CPU
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01}.Release|x86.Build.0 = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|x64.Build.0 = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Debug|x86.Build.0 = Debug|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|Any CPU.Build.0 = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x64.ActiveCfg = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x64.Build.0 = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x86.ActiveCfg = Release|Any CPU
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5}.Release|x86.Build.0 = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|Any CPU.Build.0 = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|x64.ActiveCfg = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|x64.Build.0 = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|x86.ActiveCfg = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Debug|x86.Build.0 = Debug|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|Any CPU.ActiveCfg = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|Any CPU.Build.0 = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|x64.ActiveCfg = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|x64.Build.0 = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|x86.ActiveCfg = Release|Any CPU
{490BCB11-314C-473C-9B85-A32164783507}.Release|x86.Build.0 = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x64.ActiveCfg = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x64.Build.0 = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x86.ActiveCfg = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Debug|x86.Build.0 = Debug|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|Any CPU.Build.0 = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x64.ActiveCfg = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x64.Build.0 = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x86.ActiveCfg = Release|Any CPU
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CDFA86FA-BBBA-4A5B-A833-3BE219E373E5} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
{B19FD14B-4DFE-26B6-646B-3D5D94CC4D36} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
{C734CEF7-A2AC-3076-84D8-694B7490AA9D} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
{A3579DE0-F7C5-67E8-3CF8-3AC89B64E059} = {B19FD14B-4DFE-26B6-646B-3D5D94CC4D36}
{C6034A5C-F49A-5FA4-86A6-65B2CB19613F} = {B19FD14B-4DFE-26B6-646B-3D5D94CC4D36}
{9D4F3958-FE6E-C048-E6F9-6F53D8AF03CA} = {B19FD14B-4DFE-26B6-646B-3D5D94CC4D36}
{B465D535-05D9-3A0A-08BF-35A1C18CEC46} = {B19FD14B-4DFE-26B6-646B-3D5D94CC4D36}
{A2834164-BF04-BF13-ADC5-A97145852861} = {C6034A5C-F49A-5FA4-86A6-65B2CB19613F}
{1B4FBE3A-43F5-1B1E-2877-3036AC5431EF} = {C6034A5C-F49A-5FA4-86A6-65B2CB19613F}
{DDEDC5E0-5D13-A45C-2393-A774DD4A1A07} = {A3579DE0-F7C5-67E8-3CF8-3AC89B64E059}
{51D8F471-B8EB-AD1C-0E89-AA84C5D0C759} = {A3579DE0-F7C5-67E8-3CF8-3AC89B64E059}
{773BFBD8-04CD-79F8-8301-C81308C3ED45} = {B465D535-05D9-3A0A-08BF-35A1C18CEC46}
{4B043475-1AFA-C467-FE09-A46D09CD6936} = {B465D535-05D9-3A0A-08BF-35A1C18CEC46}
{5CED3889-AECF-A6CD-55DC-F680D3C18861} = {9D4F3958-FE6E-C048-E6F9-6F53D8AF03CA}
{54BCCDE8-25E6-6FCB-4A9E-D5D2AF76D352} = {9D4F3958-FE6E-C048-E6F9-6F53D8AF03CA}
{D3BF565A-C413-4185-9528-BE1B4F46993C} = {A2834164-BF04-BF13-ADC5-A97145852861}
{3EA375C7-2900-4927-B1E5-C9D31E67F4A8} = {A2834164-BF04-BF13-ADC5-A97145852861}
{BCC8A8A6-C2ED-42D2-86BB-A05C790D7279} = {DDEDC5E0-5D13-A45C-2393-A774DD4A1A07}
{836D1466-3C20-4D74-B54A-FA09C0EE0FA2} = {DDEDC5E0-5D13-A45C-2393-A774DD4A1A07}
{BCDEAB10-6373-46E7-B408-846A3B0B508B} = {773BFBD8-04CD-79F8-8301-C81308C3ED45}
{B0EC74C5-9B2D-492C-ABAE-3E868397B122} = {773BFBD8-04CD-79F8-8301-C81308C3ED45}
{9B4BDD42-56F3-4DB9-B3E5-74ABB7C19538} = {5CED3889-AECF-A6CD-55DC-F680D3C18861}
{101FFD12-17A4-4615-9438-F347BBF4CC85} = {5CED3889-AECF-A6CD-55DC-F680D3C18861}
{AEDB3219-5E1D-4716-8DE2-F5F9391913A2} = {C734CEF7-A2AC-3076-84D8-694B7490AA9D}
{427BE8BE-DA7B-FC74-412B-547671E05463} = {CDFA86FA-BBBA-4A5B-A833-3BE219E373E5}
{C015BF35-6977-407B-8948-636A9C81C5BE} = {427BE8BE-DA7B-FC74-412B-547671E05463}
{19A89F36-FD3A-448D-90D1-04A1B67BB255} = {1B4FBE3A-43F5-1B1E-2877-3036AC5431EF}
{B27759CD-5A7D-43A4-A55C-FE1154DC4CC4} = {51D8F471-B8EB-AD1C-0E89-AA84C5D0C759}
{BD23EEF8-9196-4E0F-BF33-E14E99D34C1B} = {51D8F471-B8EB-AD1C-0E89-AA84C5D0C759}
{B52D6341-AAD9-43CB-82AF-2DBE39CBF1DB} = {51D8F471-B8EB-AD1C-0E89-AA84C5D0C759}
{0DAACE48-4EA6-4DB7-8A5C-99B86BCB1E01} = {4B043475-1AFA-C467-FE09-A46D09CD6936}
{A85AE27D-81ED-485A-BA4B-161B25BEB8A5} = {54BCCDE8-25E6-6FCB-4A9E-D5D2AF76D352}
{D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA} = {CD4A4407-C3B0-422D-BB8C-2A810CED9938}
{C4287034-6833-4505-A6EB-704A86392ECB} = {D1B6353A-63F5-4DD9-90E6-42B2CFDF1DEA}
{490BCB11-314C-473C-9B85-A32164783507} = {C4287034-6833-4505-A6EB-704A86392ECB}
{5B7BF918-E47F-4932-B5C5-E8C2C35890E4} = {C4287034-6833-4505-A6EB-704A86392ECB}
EndGlobalSection
EndGlobal
================================================
FILE: booking.rest
================================================
## uncommnet this line for use kubernetes ingress controller instead of Yarp
//@api-gateway=https://booking-microservices.com
@api-gateway=https://localhost:5000
@identity-api=http://localhost:6005
@flight-api=http://localhost:5004
@passenger-api=http://localhost:6012
@booking-api=http://localhost:6010
@contentType = application/json
@flightid = "3c5c0000-97c6-fc34-2eb9-08db322230c9"
@passengerId = "8c9c0000-97c6-fc34-2eb9-66db322230c9"
################################# Identity API #################################
###
# @name ApiRoot_Identity
GET {{identity-api}}
###
###
# @name Authenticate
POST {{api-gateway}}/identity/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=password
&client_id=client
&client_secret=secret
&username=samh
&password=Admin@123456
&scope=flight-api role
### change scope base on microservices scope (eg. passenger-api, ...)
###
###
# @name Register_New_User
POST {{api-gateway}}/identity/api/v1/identity/register-user
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"firstName": "John",
"lastName": "Do",
"username": "admin",
"passportNumber": "41290000",
"email": "admin@admin.com",
"password": "Admin@12345",
"confirmPassword": "Admin@12345"
}
###
################################# Flight API #################################
###
# @name ApiRoot_Flight
GET {{flight-api}}
###
###
# @name Create_Seat
Post {{api-gateway}}/flight/api/v1/flight/seat
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"seatNumber": "1255",
"type": 1,
"class": 1,
"flightId": "3c5c0000-97c6-fc34-2eb9-08db322230c9"
}
###
###
# @name Reserve_Seat
Post {{api-gateway}}/flight/api/v1/flight/reserve-seat
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"flightId": "3c5c0000-97c6-fc34-2eb9-08db322230c9",
"seatNumber": "1255"
}
###
###
# @name Get_Available_Seats
GET {{api-gateway}}/flight/api/v1/flight/get-available-seats/{{flightid}}
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
###
###
# @name Get_Flight_By_Id
GET {{api-gateway}}/flight/api/v1/flight/{{flightid}}
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
###
###
# @name Get_Available_Flights
GET {{api-gateway}}/flight/api/v1/flight/get-available-flights
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
###
###
# @name Create_Flights
POST {{api-gateway}}/flight/api/v1/flight
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"flightNumber": "12BB",
"aircraftId": "3c5c0000-97c6-fc34-fcd3-08db322230c8",
"departureAirportId": "3c5c0000-97c6-fc34-a0cb-08db322230c8",
"departureDate": "2022-03-01T14:55:41.255Z",
"arriveDate": "2022-03-01T14:55:41.255Z",
"arriveAirportId": "3c5c0000-97c6-fc34-fc3c-08db322230c8",
"durationMinutes": 120,
"flightDate": "2022-03-01T14:55:41.255Z",
"status": 1,
"price": 8000
}
###
###
# @name Update_Flights
PUT {{api-gateway}}/flight/api/v1/flight
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"id": 1,
"flightNumber": "BD467",
"aircraftId": "3c5c0000-97c6-fc34-fcd3-08db322230c8",
"departureAirportId": "3c5c0000-97c6-fc34-a0cb-08db322230c8",
"departureDate": "2022-04-23T12:17:45.140Z",
"arriveDate": "2022-04-23T12:17:45.140Z",
"arriveAirportId": "3c5c0000-97c6-fc34-fc3c-08db322230c8",
"durationMinutes": 120,
"flightDate": "2022-04-23T12:17:45.140Z",
"status": 4,
"isDeleted": false,
"price": 99000
}
###
###
# @name Delete_Flights
DELETE {{api-gateway}}/flight/api/v1/flight/{{flightid}}
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
###
###
# @name Create_Airport
POST {{api-gateway}}/flight/api/v1/flight/airport
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"name": "mehrabad",
"address": "tehran",
"code": "12YD"
}
###
###
# @name Create_Aircraft
POST {{api-gateway}}/flight/api/v1/flight/aircraft
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"name": "airbus2",
"model": "322",
"manufacturingYear": 2012
}
###
################################# Passenger API #################################
###
# @name ApiRoot_Passenger
GET {{passenger-api}}
###
###
# @name Complete_Registration_Passenger
POST {{api-gateway}}/passenger/api/v1/passenger/complete-registration
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"passportNumber": "41290000",
"passengerType": 1,
"age": 30
}
###
###
# @name Get_Passenger_By_Id
GET {{api-gateway}}/passenger/api/v1/passenger/{{passengerId}}
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
###
################################# Booking API #################################
###
# @name ApiRoot_Booking
GET {{booking-api}}
###
###
# @name Create_Booking
POST {{api-gateway}}/booking/api/v1/booking
accept: application/json
Content-Type: application/json
authorization: bearer {{Authenticate.response.body.access_token}}
{
"passengerId": "8c9c0000-97c6-fc34-2eb9-66db322230c9",
"flightId": "3c5c0000-97c6-fc34-2eb9-08db322230c9",
"description": "I want to fly to iran"
}
###
================================================
FILE: commitlint.config.js
================================================
module.exports = {
extends: ["@commitlint/config-conventional"],
}
================================================
FILE: deployments/configs/dashboards.md
================================================
# Dashboards
- [Introducing ASP.NET Core metrics and Grafana dashboards in .NET 8](https://devblogs.microsoft.com/dotnet/introducing-aspnetcore-metrics-and-grafana-dashboards-in-dotnet-8/)
- [ASP.NET Core](https://grafana.com/grafana/dashboards/19924-asp-net-core/)
- [ASP.NET Core Endpoint](https://grafana.com/grafana/dashboards/19925-asp-net-core-endpoint/)
- [Node Exporter Quickstart and Dashboard](https://grafana.com/grafana/dashboards/13978-node-exporter-quickstart-and-dashboard/)
- [PostgreSQL Exporter Quickstart and Dashboard](https://grafana.com/grafana/dashboards/14114-postgres-overview/)
- [RabbitMQ-Overview](https://grafana.com/grafana/dashboards/10991-rabbitmq-overview/)
================================================
FILE: deployments/configs/grafana/dashboards/dotnet-core-endpoint.json
================================================
{
"__inputs": [
{
"name": "DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY",
"label": "Managed_Prometheus_jamesnk-telemetry",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.4.8"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [
{
"asDropdown": false,
"icon": "dashboard",
"includeVars": false,
"keepTime": true,
"tags": [],
"targetBlank": false,
"title": " ASP.NET Core",
"tooltip": "",
"type": "link",
"url": "/d/KdDACDp4z/asp-net-core-metrics"
}
],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "dark-green",
"mode": "continuous-GrYlRd",
"seriesBy": "max"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "smooth",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "s"
},
"overrides": [
{
"__systemRef": "hideSeriesFrom",
"matcher": {
"id": "byNames",
"options": {
"mode": "exclude",
"names": [
"p50"
],
"prefix": "All except:",
"readOnly": true
}
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"legend": false,
"tooltip": false,
"viz": false
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 40,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.50, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"legendFormat": "p50",
"range": true,
"refId": "p50"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.75, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p75",
"range": true,
"refId": "p75"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.90, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p90",
"range": true,
"refId": "p90"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.95, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p95",
"range": true,
"refId": "p95"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.98, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p98",
"range": true,
"refId": "p98"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.99, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p99",
"range": true,
"refId": "p99"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.999, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m])) by (le))",
"hide": false,
"legendFormat": "p99.9",
"range": true,
"refId": "p99.9"
}
],
"title": "Requests Duration - $method $route",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic",
"seriesBy": "max"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "smooth",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "percentunit"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "All"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "dark-orange",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "4XX"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "yellow",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "5XX"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "dark-red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 0
},
"id": 46,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\", status_code=~\"4..|5..\"}[5m]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m]))",
"legendFormat": "All",
"range": true,
"refId": "All"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\", status_code=~\"4..\"}[5m]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m]))",
"hide": false,
"legendFormat": "4XX",
"range": true,
"refId": "4XX"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\", status_code=~\"5..\"}[5m]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[5m]))",
"hide": false,
"legendFormat": "5XX",
"range": true,
"refId": "5XX"
}
],
"title": "Errors Rate - $method $route",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Requests"
},
"properties": [
{
"id": "custom.width",
"value": 300
},
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "gauge"
}
},
{
"id": "color",
"value": {
"mode": "continuous-YlRd"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Route"
},
"properties": [
{
"id": "links",
"value": [
{
"title": "",
"url": "/d/NagEsjE4z/asp-net-core-endpoint-details?var-route=${__data.fields.Route}&var-method=${__data.fields.Method}&${__url_time_range}"
}
]
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"hideTimeOverride": false,
"id": 44,
"options": {
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum by (exception_name) (\r\n max_over_time(http_server_request_duration_s_count{route=\"$route\", method=\"$method\", exception_name!=\"\"}[$__rate_interval])\r\n)",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{route}}",
"range": false,
"refId": "A"
}
],
"title": "Unhandled Exceptions",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"method": false
},
"indexByName": {
"Time": 0,
"Value": 2,
"exception_name": 1
},
"renameByName": {
"Value": "Requests",
"exception_name": "Exception",
"method": "Method",
"route": "Route"
}
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "blue",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 12,
"x": 12,
"y": 9
},
"id": 42,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"max"
],
"fields": "",
"values": false
},
"textMode": "value_and_name"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum by (status_code) (\r\n max_over_time(http_server_request_duration_s_count{route=\"$route\", method=\"$method\"}[$__rate_interval])\r\n )",
"legendFormat": "Status {{status_code}}",
"range": true,
"refId": "A"
}
],
"title": "Requests HTTP Status Code",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "green",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 13
},
"id": 48,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"max"
],
"fields": "",
"values": false
},
"textMode": "value_and_name"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum by (scheme) (\r\n max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[$__rate_interval])\r\n )",
"legendFormat": "{{scheme}}",
"range": true,
"refId": "A"
}
],
"title": "Requests Secured",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "purple",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 13
},
"id": 50,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"max"
],
"fields": "",
"values": false
},
"textMode": "value_and_name"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum by (protocol) (\r\n max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route=\"$route\", method=\"$method\"}[$__rate_interval])\r\n )",
"legendFormat": "{{protocol}}",
"range": true,
"refId": "A"
}
],
"title": "Requests HTTP Protocol",
"type": "stat"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [
"dotnet",
"prometheus",
"aspnetcore"
],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_current_requests, job)",
"hide": 0,
"includeAll": false,
"label": "Job",
"multi": false,
"name": "job",
"options": [],
"query": {
"query": "label_values(http_server_current_requests, job)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_current_requests{job=~\"$job\"}, instance)",
"hide": 0,
"includeAll": false,
"label": "Instance",
"multi": false,
"name": "instance",
"options": [],
"query": {
"query": "label_values(http_server_current_requests{job=~\"$job\"}, instance)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_request_duration_s_count, route)",
"description": "Route",
"hide": 0,
"includeAll": false,
"label": "Route",
"multi": false,
"name": "route",
"options": [],
"query": {
"query": "label_values(http_server_request_duration_s_count, route)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_request_duration_s_count{route=~\"$route\"}, method)",
"hide": 0,
"includeAll": false,
"label": "Method",
"multi": false,
"name": "method",
"options": [],
"query": {
"query": "label_values(http_server_request_duration_s_count{route=~\"$route\"}, method)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "ASP.NET Core Endpoint",
"uid": "NagEsjE4z",
"version": 10,
"weekStart": ""
}
================================================
FILE: deployments/configs/grafana/dashboards/dotnet-core.json
================================================
{
"__inputs": [
{
"name": "DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY",
"label": "Managed_Prometheus_jamesnk-telemetry",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.4.8"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "ASP.NET Core metrics from OpenTelemetry NuGet",
"editable": true,
"fiscalYearStartMonth": 0,
"gnetId": 17706,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "dark-green",
"mode": "continuous-GrYlRd",
"seriesBy": "max"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "smooth",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "s"
},
"overrides": [
{
"__systemRef": "hideSeriesFrom",
"matcher": {
"id": "byNames",
"options": {
"mode": "exclude",
"names": [
"p50"
],
"prefix": "All except:",
"readOnly": true
}
},
"properties": [
{
"id": "custom.hideFrom",
"value": {
"legend": false,
"tooltip": false,
"viz": false
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 40,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.50, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"legendFormat": "p50",
"range": true,
"refId": "p50"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.75, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p75",
"range": true,
"refId": "p75"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.90, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p90",
"range": true,
"refId": "p90"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.95, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p95",
"range": true,
"refId": "p95"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.98, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p98",
"range": true,
"refId": "p98"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.99, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p99",
"range": true,
"refId": "p99"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "histogram_quantile(0.999, sum(rate(http_server_request_duration_s_bucket{job=\"$job\", instance=\"$instance\"}[$__rate_interval])) by (le))",
"hide": false,
"legendFormat": "p99.9",
"range": true,
"refId": "p99.9"
}
],
"title": "Requests Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic",
"seriesBy": "max"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 50,
"gradientMode": "opacity",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "smooth",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "percentunit"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "All"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "dark-orange",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "4XX"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "yellow",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "5XX"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "dark-red",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 0
},
"id": 47,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", status_code=~\"4..|5..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
"legendFormat": "All",
"range": true,
"refId": "All"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", status_code=~\"4..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
"hide": false,
"legendFormat": "4XX",
"range": true,
"refId": "4XX"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", status_code=~\"5..\"}[$__rate_interval]) or vector(0)) / sum(rate(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval]))",
"hide": false,
"legendFormat": "5XX",
"range": true,
"refId": "5XX"
}
],
"title": "Errors Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 0,
"y": 9
},
"id": 49,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(kestrel_current_connections{job=\"$job\", instance=\"$instance\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Current Connections",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 6,
"y": 9
},
"id": 55,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum(http_server_current_requests{job=\"$job\", instance=\"$instance\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Current Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "blue",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 9
},
"id": 58,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {},
"textMode": "value"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"})",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Total Requests",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "dark-red",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 9
},
"id": 59,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": {},
"textMode": "value"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", exception_name!=\"\"})",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Total Unhandled Exceptions",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "green",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 13
},
"id": 60,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"max"
],
"fields": "",
"values": false
},
"textMode": "value_and_name"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum by (scheme) (\r\n max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval])\r\n )",
"legendFormat": "{{scheme}}",
"range": true,
"refId": "A"
}
],
"title": "Requests Secured",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "purple",
"mode": "fixed"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 13
},
"id": 42,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"max"
],
"fields": "",
"values": false
},
"textMode": "value_and_name"
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"expr": "sum by (protocol) (\r\n max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\"}[$__rate_interval])\r\n )",
"legendFormat": "{{protocol}}",
"range": true,
"refId": "A"
}
],
"title": "Requests HTTP Protocol",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Requests"
},
"properties": [
{
"id": "custom.width",
"value": 300
},
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "gauge"
}
},
{
"id": "color",
"value": {
"mode": "continuous-BlPu"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Endpoint"
},
"properties": [
{
"id": "links",
"value": [
{
"targetBlank": false,
"title": "Test",
"url": "/d/NagEsjE4z/asp-net-core-endpoint-details?var-route=${__data.fields.route}&var-method=${__data.fields.method}&${__url_time_range}"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "route"
},
"properties": [
{
"id": "custom.hidden",
"value": true
}
]
},
{
"matcher": {
"id": "byName",
"options": "method"
},
"properties": [
{
"id": "custom.hidden",
"value": true
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 17
},
"hideTimeOverride": false,
"id": 51,
"options": {
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"exemplar": false,
"expr": " topk(10,\r\n sum by (route, method, method_route) (\r\n label_join(max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route!=\"\"}[$__rate_interval]), \"method_route\", \" \", \"method\", \"route\")\r\n ))",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{route}}",
"range": false,
"refId": "A"
}
],
"title": "Top 10 Requested Endpoints",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"method": false,
"route": false
},
"indexByName": {
"Time": 0,
"Value": 4,
"method": 2,
"method_route": 3,
"route": 1
},
"renameByName": {
"Value": "Requests",
"method": "",
"method_route": "Endpoint",
"route": ""
}
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Requests"
},
"properties": [
{
"id": "custom.width",
"value": 300
},
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "gauge"
}
},
{
"id": "color",
"value": {
"mode": "continuous-YlRd"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Endpoint"
},
"properties": [
{
"id": "links",
"value": [
{
"title": "",
"url": "/d/NagEsjE4z/asp-net-core-endpoint-details?var-route=${__data.fields.route}&var-method=${__data.fields.method}&${__url_time_range}"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "route"
},
"properties": [
{
"id": "custom.hidden",
"value": true
}
]
},
{
"matcher": {
"id": "byName",
"options": "method"
},
"properties": [
{
"id": "custom.hidden",
"value": true
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 17
},
"hideTimeOverride": false,
"id": 54,
"options": {
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.4.8",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"editorMode": "code",
"exemplar": false,
"expr": " topk(10,\r\n sum by (route, method, method_route) (\r\n label_join(max_over_time(http_server_request_duration_s_count{job=\"$job\", instance=\"$instance\", route!=\"\", exception_name!=\"\"}[$__rate_interval]), \"method_route\", \" \", \"method\", \"route\")\r\n ))",
"format": "table",
"instant": true,
"interval": "",
"legendFormat": "{{route}}",
"range": false,
"refId": "A"
}
],
"title": "Top 10 Unhandled Exception Endpoints",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"method": false
},
"indexByName": {
"Time": 0,
"Value": 4,
"method": 2,
"method_route": 3,
"route": 1
},
"renameByName": {
"Value": "Requests",
"method": "",
"method_route": "Endpoint",
"route": ""
}
}
}
],
"type": "table"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [
"dotnet",
"prometheus",
"aspnetcore"
],
"templating": {
"list": [
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_current_requests, job)",
"hide": 0,
"includeAll": false,
"label": "Job",
"multi": false,
"name": "job",
"options": [],
"query": {
"query": "label_values(http_server_current_requests, job)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_MANAGED_PROMETHEUS_JAMESNK-TELEMETRY}"
},
"definition": "label_values(http_server_current_requests{job=~\"$job\"}, instance)",
"hide": 0,
"includeAll": false,
"label": "Instance",
"multi": false,
"name": "instance",
"options": [],
"query": {
"query": "label_values(http_server_current_requests{job=~\"$job\"}, instance)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "ASP.NET Core",
"uid": "KdDACDp4z",
"version": 5,
"weekStart": ""
}
================================================
FILE: deployments/configs/grafana/dashboards/node-exporter.json
================================================
{
"__inputs": [],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.4.3"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
}
],
"annotations": {
"list": []
},
"editable": false,
"gnetId": 13978,
"graphTooltip": 0,
"hideControls": false,
"id": null,
"links": [],
"refresh": "",
"rows": [
{
"collapse": false,
"collapsed": false,
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"fillGradient": 0,
"gridPos": {},
"id": 2,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "(\n (1 - rate(node_cpu_seconds_total{job=\"node\", mode=\"idle\", instance=\"$instance\"}[$__interval]))\n/ ignoring(cpu) group_left\n count without (cpu)( node_cpu_seconds_total{job=\"node\", mode=\"idle\", instance=\"$instance\"})\n)\n",
"format": "time_series",
"interval": "1m",
"intervalFactor": 5,
"legendFormat": "{{cpu}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "CPU Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "percentunit",
"label": null,
"logBase": 1,
"max": 1,
"min": 0,
"show": true
},
{
"format": "percentunit",
"label": null,
"logBase": 1,
"max": 1,
"min": 0,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 0,
"fillGradient": 0,
"gridPos": {},
"id": 3,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "node_load1{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "1m load average",
"refId": "A"
},
{
"expr": "node_load5{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "5m load average",
"refId": "B"
},
{
"expr": "node_load15{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "15m load average",
"refId": "C"
},
{
"expr": "count(node_cpu_seconds_total{job=\"node\", instance=\"$instance\", mode=\"idle\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "logical cores",
"refId": "D"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Load Average",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": false,
"title": "Dashboard Row",
"titleSize": "h6",
"type": "row"
},
{
"collapse": false,
"collapsed": false,
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"fillGradient": 0,
"gridPos": {},
"id": 4,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [],
"spaceLength": 10,
"span": 9,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "(\n node_memory_MemTotal_bytes{job=\"node\", instance=\"$instance\"}\n-\n node_memory_MemFree_bytes{job=\"node\", instance=\"$instance\"}\n-\n node_memory_Buffers_bytes{job=\"node\", instance=\"$instance\"}\n-\n node_memory_Cached_bytes{job=\"node\", instance=\"$instance\"}\n)\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "memory used",
"refId": "A"
},
{
"expr": "node_memory_Buffers_bytes{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "memory buffers",
"refId": "B"
},
{
"expr": "node_memory_Cached_bytes{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "memory cached",
"refId": "C"
},
{
"expr": "node_memory_MemFree_bytes{job=\"node\", instance=\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "memory free",
"refId": "D"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Memory Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"rgba(50, 172, 45, 0.97)",
"rgba(237, 129, 40, 0.89)",
"rgba(245, 54, 54, 0.9)"
],
"datasource": "$datasource",
"format": "percent",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": true,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {},
"id": 5,
"interval": null,
"links": [],
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"span": 3,
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": false
},
"tableColumn": "",
"targets": [
{
"expr": "100 -\n(\n avg(node_memory_MemAvailable_bytes{job=\"node\", instance=\"$instance\"})\n/\n avg(node_memory_MemTotal_bytes{job=\"node\", instance=\"$instance\"})\n* 100\n)\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "",
"refId": "A"
}
],
"thresholds": "80, 90",
"title": "Memory Usage",
"type": "singlestat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "current"
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": false,
"title": "Dashboard Row",
"titleSize": "h6",
"type": "row"
},
{
"collapse": false,
"collapsed": false,
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 0,
"fillGradient": 0,
"gridPos": {},
"id": 6,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [
{
"alias": "/ read| written/",
"yaxis": 1
},
{
"alias": "/ io time/",
"yaxis": 2
}
],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(node_disk_read_bytes_total{job=\"node\", instance=\"$instance\", device!=\"\"}[$__interval])",
"format": "time_series",
"interval": "1m",
"intervalFactor": 2,
"legendFormat": "{{device}} read",
"refId": "A"
},
{
"expr": "rate(node_disk_written_bytes_total{job=\"node\", instance=\"$instance\", device!=\"\"}[$__interval])",
"format": "time_series",
"interval": "1m",
"intervalFactor": 2,
"legendFormat": "{{device}} written",
"refId": "B"
},
{
"expr": "rate(node_disk_io_time_seconds_total{job=\"node\", instance=\"$instance\", device!=\"\"}[$__interval])",
"format": "time_series",
"interval": "1m",
"intervalFactor": 2,
"legendFormat": "{{device}} io time",
"refId": "C"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Disk I/O",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "s",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"fillGradient": 0,
"gridPos": {},
"id": 7,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [
{
"alias": "used",
"color": "#E0B400"
},
{
"alias": "available",
"color": "#73BF69"
}
],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum(\n max by (device) (\n node_filesystem_size_bytes{job=\"node\", instance=\"$instance\", fstype!=\"\"}\n -\n node_filesystem_avail_bytes{job=\"node\", instance=\"$instance\", fstype!=\"\"}\n )\n)\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "used",
"refId": "A"
},
{
"expr": "sum(\n max by (device) (\n node_filesystem_avail_bytes{job=\"node\", instance=\"$instance\", fstype!=\"\"}\n )\n)\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "available",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Disk Space Usage",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": false,
"title": "Dashboard Row",
"titleSize": "h6",
"type": "row"
},
{
"collapse": false,
"collapsed": false,
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 0,
"fillGradient": 0,
"gridPos": {},
"id": 8,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(node_network_receive_bytes_total{job=\"node\", instance=\"$instance\", device!=\"lo\"}[$__interval])",
"format": "time_series",
"interval": "1m",
"intervalFactor": 2,
"legendFormat": "{{device}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Network Received",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 0,
"fillGradient": 0,
"gridPos": {},
"id": 9,
"legend": {
"alignAsTable": false,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": false,
"show": true,
"sideWidth": null,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"repeat": null,
"seriesOverrides": [],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(node_network_transmit_bytes_total{job=\"node\", instance=\"$instance\", device!=\"lo\"}[$__interval])",
"format": "time_series",
"interval": "1m",
"intervalFactor": 2,
"legendFormat": "{{device}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Network Transmitted",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": false,
"title": "Dashboard Row",
"titleSize": "h6",
"type": "row"
}
],
"schemaVersion": 14,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"label": null,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"allValue": null,
"current": {},
"datasource": "$datasource",
"hide": 0,
"includeAll": false,
"label": null,
"multi": false,
"name": "instance",
"options": [],
"query": "label_values(node_exporter_build_info{job=\"node\"}, instance)",
"refresh": 2,
"regex": "",
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "Node Exporter Quickstart and Dashboard",
"version": 0,
"description": "A quickstart to setup Prometheus Node Exporter with preconfigured dashboards, alerting rules, and recording rules."
}
================================================
FILE: deployments/configs/grafana/dashboards/postgresql.json
================================================
{
"__inputs": [],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.2.0"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "A quickstart to setup the Prometheus PostgreSQL Exporter with preconfigured dashboards, alerting rules, and recording rules.",
"editable": true,
"gnetId": 14114,
"graphTooltip": 0,
"id": 1,
"iteration": 1603191461722,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 20,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 1,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"max": true,
"min": true,
"rightSide": true,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "fetched",
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_tup_fetched{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "fetched",
"measurement": "postgresql",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"tup_fetched"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 120,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "fetched",
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_tup_returned{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "returned",
"measurement": "postgresql",
"policy": "default",
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"tup_fetched"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 120,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "fetched",
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_tup_inserted{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "inserted",
"measurement": "postgresql",
"policy": "default",
"refId": "C",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"tup_fetched"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 120,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "fetched",
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_tup_updated{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "updated",
"measurement": "postgresql",
"policy": "default",
"refId": "D",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"tup_fetched"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 120,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "fetched",
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_tup_deleted{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "deleted",
"measurement": "postgresql",
"policy": "default",
"refId": "E",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"tup_fetched"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 120,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Rows",
"tooltip": {
"msResolution": true,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"cacheTimeout": null,
"colorBackground": false,
"colorValue": false,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"datasource": "$datasource",
"decimals": 0,
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"format": "none",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
},
"gridPos": {
"h": 3,
"w": 4,
"x": 20,
"y": 0
},
"height": "55px",
"id": 11,
"interval": null,
"isNew": true,
"links": [],
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"nullText": null,
"postfix": "",
"postfixFontSize": "50%",
"prefix": "",
"prefixFontSize": "50%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": true,
"lineColor": "rgb(31, 120, 193)",
"show": true
},
"tableColumn": "",
"targets": [
{
"dsType": "prometheus",
"expr": "sum(irate(pg_stat_database_xact_commit{datname=~\"$db\",instance=~\"$instance\"}[5m])) + sum(irate(pg_stat_database_xact_rollback{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"measurement": "postgresql",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"xact_commit"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [
"10s"
],
"type": "non_negative_derivative"
}
]
],
"step": 1800,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
}
],
"thresholds": "",
"title": "QPS",
"transparent": true,
"type": "singlestat",
"valueFontSize": "80%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"decimals": 1,
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 7
},
"hiddenSeries": false,
"id": 2,
"isNew": true,
"legend": {
"alignAsTable": true,
"avg": true,
"current": false,
"hideZero": true,
"max": true,
"min": true,
"rightSide": false,
"show": true,
"total": false,
"values": true
},
"lines": true,
"linewidth": 1,
"links": [],
"nullPointMode": "connected",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "Buffers Allocated",
"dsType": "prometheus",
"expr": "irate(pg_stat_bgwriter_buffers_alloc{instance=~'$instance'}[5m])",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "buffers_alloc",
"measurement": "postgresql",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"buffers_alloc"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "Buffers Allocated",
"dsType": "prometheus",
"expr": "irate(pg_stat_bgwriter_buffers_backend_fsync{instance=~'$instance'}[5m])",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "buffers_backend_fsync",
"measurement": "postgresql",
"policy": "default",
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"buffers_alloc"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "Buffers Allocated",
"dsType": "prometheus",
"expr": "irate(pg_stat_bgwriter_buffers_backend{instance=~'$instance'}[5m])",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "buffers_backend",
"measurement": "postgresql",
"policy": "default",
"refId": "C",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"buffers_alloc"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "Buffers Allocated",
"dsType": "prometheus",
"expr": "irate(pg_stat_bgwriter_buffers_clean{instance=~'$instance'}[5m])",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "buffers_clean",
"measurement": "postgresql",
"policy": "default",
"refId": "D",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"buffers_alloc"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "Buffers Allocated",
"dsType": "prometheus",
"expr": "irate(pg_stat_bgwriter_buffers_checkpoint{instance=~'$instance'}[5m])",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "buffers_checkpoint",
"measurement": "postgresql",
"policy": "default",
"refId": "E",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"buffers_alloc"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Buffers",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 7
},
"hiddenSeries": false,
"id": 3,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "conflicts",
"dsType": "prometheus",
"expr": "sum(rate(pg_stat_database_deadlocks{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "deadlocks",
"measurement": "postgresql",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"conflicts"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
},
{
"alias": "deadlocks",
"dsType": "prometheus",
"expr": "sum(rate(pg_stat_database_conflicts{datname=~\"$db\",instance=~\"$instance\"}[5m]))",
"format": "time_series",
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"intervalFactor": 2,
"legendFormat": "conflicts",
"measurement": "postgresql",
"policy": "default",
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"deadlocks"
],
"type": "field"
},
{
"params": [],
"type": "mean"
},
{
"params": [],
"type": "difference"
}
]
],
"step": 240,
"tags": [
{
"key": "instance",
"operator": "=~",
"value": "/^$instance$/"
}
]
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Conflicts/Deadlocks",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 12,
"x": 0,
"y": 14
},
"hiddenSeries": false,
"id": 12,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"alertThreshold": true
},
"percentage": true,
"pluginVersion": "7.2.1",
"pointradius": 1,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum(pg_stat_database_blks_hit{datname=~\"$db\",instance=~\"$instance\"}) / (sum(pg_stat_database_blks_hit{datname=~\"$db\",instance=~\"$instance\"}) + sum(pg_stat_database_blks_read{datname=~\"$db\",instance=~\"$instance\"}))",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "cache hit rate",
"refId": "A",
"step": 240
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Cache hit ratio",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "percentunit",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"editable": true,
"error": false,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"grid": {},
"gridPos": {
"h": 7,
"w": 12,
"x": 12,
"y": 14
},
"hiddenSeries": false,
"id": 13,
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.1",
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "pg_stat_database_numbackends{datname=~\"$db\",instance=~\"$instance\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{__name__}}",
"refId": "A",
"step": 240
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Number of active connections",
"tooltip": {
"msResolution": false,
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": false,
"schemaVersion": 26,
"style": "dark",
"tags": [
"postgres"
],
"templating": {
"list": [
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": "$datasource",
"definition": "",
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "instance",
"options": [],
"query": "label_values(up{job=~\"postgres.*\"},instance)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false,
"text": "All",
"value": "$__all"
},
"datasource": "$datasource",
"definition": "label_values(pg_stat_database_tup_fetched{instance=~\"$instance\",datname!~\"template.*|postgres\"},datname)",
"hide": 0,
"includeAll": true,
"label": "db",
"multi": false,
"name": "db",
"options": [],
"query": "label_values(pg_stat_database_tup_fetched{instance=~\"$instance\",datname!~\"template.*|postgres\"},datname)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"selected": false,
"text": "Postgres Overview",
"value": "Postgres Overview"
},
"hide": 0,
"includeAll": false,
"label": "datasource",
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": null,
"current": {
"selected": true,
"text": "postgres",
"value": "postgres"
},
"datasource": "$datasource",
"definition": "label_values(pg_up, job)",
"hide": 0,
"includeAll": false,
"label": "job",
"multi": false,
"name": "job",
"options": [
{
"selected": true,
"text": "postgres",
"value": "postgres"
}
],
"query": "label_values(pg_up, job)",
"refresh": 0,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "browser",
"title": "PostgreSQL Exporter Quickstart and Dashboard",
"uid": "wGgaPlciz",
"version": 5
}
================================================
FILE: deployments/configs/grafana/dashboards/rabbitmq.json
================================================
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": [],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "8.3.4"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "A new RabbitMQ Management Overview",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"iteration": 1659711638455,
"links": [
{
"icon": "doc",
"tags": [],
"targetBlank": true,
"title": "Monitoring with Prometheus & Grafana",
"tooltip": "",
"type": "link",
"url": "https://www.rabbitmq.com/prometheus.html"
}
],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#37872D",
"value": null
},
{
"color": "#1F60C4",
"value": 10000
},
{
"color": "#C4162A",
"value": 100000
}
]
},
"unit": "short"
},
"overrides": []
},
"id": 64,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_queue_messages_ready * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"hide": false,
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Ready messages",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": -1
},
{
"color": "#37872D",
"value": 50
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 6,
"y": 0
},
"id": 62,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_received_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Incoming messages / s",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": 0
},
{
"color": "#37872D",
"value": 10
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 12,
"y": 0
},
"id": 66,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_global_publishers * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Publishers",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": 0
},
{
"color": "#37872D",
"value": 10
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 16,
"y": 0
},
"id": 37,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_connections * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Connections",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": 0
},
{
"color": "#37872D",
"value": 10
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 20,
"y": 0
},
"id": 40,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_queues * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Queues",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#37872D",
"value": null
},
{
"color": "#1F60C4",
"value": 100
},
{
"color": "#C4162A",
"value": 500
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 0,
"y": 3
},
"id": 65,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_queue_messages_unacked * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"hide": false,
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Unacknowledged messages",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": -1
},
{
"color": "#37872D",
"value": 50
}
]
},
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 6,
"y": 3
},
"id": 63,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_redelivered_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) +\nsum(rate(rabbitmq_global_messages_delivered_consume_auto_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) +\nsum(rate(rabbitmq_global_messages_delivered_consume_manual_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) +\nsum(rate(rabbitmq_global_messages_delivered_get_auto_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) +\nsum(rate(rabbitmq_global_messages_delivered_get_manual_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"hide": false,
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Outgoing messages / s",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": 0
},
{
"color": "#37872D",
"value": 10
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 12,
"y": 3
},
"id": 41,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_consumers * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Consumers",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#C4162A",
"value": null
},
{
"color": "#1F60C4",
"value": 0
},
{
"color": "#37872D",
"value": 10
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 16,
"y": 3
},
"id": 38,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_channels * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Channels",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "#1F60C4",
"value": null
},
{
"color": "#37872D",
"value": 3
},
{
"color": "#C4162A",
"value": 8
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 4,
"x": 20,
"y": 3
},
"id": 67,
"links": [],
"maxDataPoints": 100,
"options": {
"colorMode": "background",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_build_info * on(instance, job) group_left(rabbitmq_cluster) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"title": "Nodes",
"type": "stat"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 6
},
"id": 4,
"panels": [],
"title": "NODES",
"type": "row"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"displayMode": "auto"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "erlang_version"
},
"properties": [
{
"id": "displayName",
"value": "Erlang/OTP"
},
{
"id": "unit",
"value": "none"
},
{
"id": "custom.align"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "rgba(50, 172, 45, 0.97)",
"value": null
},
{
"color": "rgba(237, 129, 40, 0.89)"
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "rabbitmq_version"
},
"properties": [
{
"id": "displayName",
"value": "RabbitMQ"
},
{
"id": "unit",
"value": "none"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "rgba(245, 54, 54, 0.9)",
"value": null
},
{
"color": "rgba(237, 129, 40, 0.89)"
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "instance"
},
"properties": [
{
"id": "displayName",
"value": "Host"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "rabbitmq_node"
},
"properties": [
{
"id": "displayName",
"value": "Node name"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "rgba(245, 54, 54, 0.9)",
"value": null
},
{
"color": "rgba(237, 129, 40, 0.89)"
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "Value"
},
"properties": [
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "job"
},
"properties": [
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "rabbitmq_cluster"
},
"properties": [
{
"id": "displayName",
"value": "Cluster"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "prometheus_client_version"
},
"properties": [
{
"id": "displayName",
"value": "prometheus.erl"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
},
{
"matcher": {
"id": "byName",
"options": "prometheus_plugin_version"
},
"properties": [
{
"id": "displayName",
"value": "rabbitmq_prometheus"
},
{
"id": "unit",
"value": "short"
},
{
"id": "decimals",
"value": 2
},
{
"id": "custom.align"
}
]
}
]
},
"gridPos": {
"h": 4,
"w": 24,
"x": 0,
"y": 7
},
"id": 69,
"links": [],
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true
},
"pluginVersion": "8.3.4",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"exemplar": false,
"expr": "rabbitmq_build_info * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}",
"format": "table",
"instant": true,
"interval": "",
"intervalFactor": 1,
"legendFormat": "",
"refId": "A"
}
],
"transformations": [
{
"id": "merge",
"options": {
"reducers": []
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "If the value is zero or less, the memory alarm will be triggered and all publishing connections across all cluster nodes will be blocked.\n\nThis value can temporarily go negative because the memory alarm is triggered with a slight delay.\n\nThe kernel's view of the amount of memory used by the node can differ from what the node itself can observe. This means that this value can be negative for a sustained period of time.\n\nBy default nodes use resident set size (RSS) to compute how much memory they use. This strategy can be changed (see the guides below).\n\n* [Alarms](https://www.rabbitmq.com/alarms.html)\n* [Memory Alarms](https://www.rabbitmq.com/memory.html)\n* [Reasoning About Memory Use](https://www.rabbitmq.com/memory-use.html)\n* [Blocked Connection Notifications](https://www.rabbitmq.com/connection-blocked.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"decimals": 1,
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 0
},
{
"color": "transparent",
"value": 536870912
}
]
},
"unit": "bytes"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 11
},
"id": 7,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "(rabbitmq_resident_memory_limit_bytes * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) -\n(rabbitmq_process_resident_memory_bytes * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Memory available before publishers blocked",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "This metric is reported for the partition where the RabbitMQ data directory is stored.\n\nIf the value is zero or less, the disk alarm will be triggered and all publishing connections across all cluster nodes will be blocked.\n\nThis value can temporarily go negative because the free disk space alarm is triggered with a slight delay.\n\n* [Alarms](https://www.rabbitmq.com/alarms.html)\n* [Disk Space Alarms](https://www.rabbitmq.com/disk-alarms.html)\n* [Disk Space](https://www.rabbitmq.com/production-checklist.html#resource-limits-disk-space)\n* [Persistence Configuration](https://www.rabbitmq.com/persistence-conf.html)\n* [Blocked Connection Notifications](https://www.rabbitmq.com/connection-blocked.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"decimals": 1,
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 1073741824
},
{
"color": "transparent",
"value": 5368709120
}
]
},
"unit": "bytes"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 8,
"x": 12,
"y": 11
},
"id": 8,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "rabbitmq_disk_space_available_bytes * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Disk space available before publishers blocked",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "When this value reaches zero, new connections will not be accepted and disk write operations may fail.\n\nClient libraries, peer nodes and CLI tools will not be able to connect when the node runs out of available file descriptors.\n\n* [Open File Handles Limit](https://www.rabbitmq.com/production-checklist.html#resource-limits-file-handle-limit)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 500
},
{
"color": "transparent",
"value": 1000
}
]
},
"unit": "none"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 4,
"w": 4,
"x": 20,
"y": 11
},
"id": 2,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "(rabbitmq_process_max_fds * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) -\n(rabbitmq_process_open_fds * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "File descriptors available",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "When this value reaches zero, new connections will not be accepted.\n\nClient libraries, peer nodes and CLI tools will not be able to connect when the node runs out of available file descriptors.\n\n* [Networking and RabbitMQ](https://www.rabbitmq.com/networking.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 500
},
{
"color": "transparent",
"value": 1000
}
]
},
"unit": "none"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 4,
"w": 4,
"x": 20,
"y": 15
},
"id": 5,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "(rabbitmq_process_max_tcp_sockets * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) -\n(rabbitmq_process_open_tcp_sockets * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "TCP sockets available",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 19
},
"id": 27,
"panels": [],
"title": "QUEUED MESSAGES",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "Total number of ready messages ready to be delivered to consumers.\n\nAim to keep this value as low as possible. RabbitMQ behaves best when messages are flowing through it. It's OK for publishers to occasionally outpace consumers, but the expectation is that consumers will eventually process all ready messages.\n\nIf this metric keeps increasing, your system will eventually run out of memory and/or disk space. Consider using TTL or Queue Length Limit to prevent unbounded message growth.\n\n* [Queues](https://www.rabbitmq.com/queues.html)\n* [Consumers](https://www.rabbitmq.com/consumers.html)\n* [Queue Length Limit](https://www.rabbitmq.com/maxlength.html)\n* [Time-To-Live and Expiration](https://www.rabbitmq.com/ttl.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 20
},
"id": 9,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_queue_messages_ready * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages ready to be delivered to consumers",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The total number of messages that are either in-flight to consumers, currently being processed by consumers or simply waiting for the consumer acknowledgements to be processed by the queue. Until the queue processes the message acknowledgement, the message will remain unacknowledged.\n\n* [Queues](https://www.rabbitmq.com/queues.html)\n* [Confirms and Acknowledgements](https://www.rabbitmq.com/confirms.html)\n* [Consumer Prefetch](https://www.rabbitmq.com/consumer-prefetch.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 20
},
"id": 19,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rabbitmq_queue_messages_unacked * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages pending consumer acknowledgement",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 25
},
"id": 11,
"panels": [],
"title": "INCOMING MESSAGES",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The incoming message rate before any routing rules are applied.\n\nIf this value is lower than the number of messages published to queues, it may indicate that some messages are delivered to more than one queue.\n\nIf this value is higher than the number of messages published to queues, messages cannot be routed and will either be dropped or returned to publishers.\n\n* [Publishers](https://www.rabbitmq.com/publishers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 26
},
"id": 13,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_received_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages published / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages confirmed by the broker to publishers. Publishers must opt-in to receive message confirmations.\n\nIf this metric is consistently at zero it may suggest that publisher confirms are not used by clients. The safety of published messages is likely to be at risk.\n\n* [Publisher Confirms](https://www.rabbitmq.com/confirms.html#publisher-confirms)\n* [Publisher Confirms and Data Safety](https://www.rabbitmq.com/publishers.html#data-safety)\n* [When Will Published Messages Be Confirmed by the Broker?](https://www.rabbitmq.com/confirms.html#when-publishes-are-confirmed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 26
},
"id": 18,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_confirmed_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages confirmed to publishers / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages received from publishers and successfully routed to the master queue replicas.\n\n* [Queues](https://www.rabbitmq.com/queues.html)\n* [Publishers](https://www.rabbitmq.com/publishers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 31
},
"id": 61,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_routed_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages routed to queues / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages received from publishers that have publisher confirms enabled and the broker has not confirmed yet.\n\n* [Publishers](https://www.rabbitmq.com/publishers.html)\n* [Confirms and Acknowledgements](https://www.rabbitmq.com/confirms.html)\n* [When Will Published Messages Be Confirmed by the Broker?](https://www.rabbitmq.com/confirms.html#when-publishes-are-confirmed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 31
},
"id": 12,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_received_confirm_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"} - \nrate(rabbitmq_global_messages_confirmed_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}\n) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages unconfirmed to publishers / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages that cannot be routed and are dropped. \n\nAny value above zero means message loss and likely suggests a routing problem on the publisher end.\n\n* [Unroutable Message Handling](https://www.rabbitmq.com/publishers.html#unroutable)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 0
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/rabbit/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#C4162A",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 36
},
"id": 34,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_unroutable_dropped_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Unroutable messages dropped / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages that cannot be routed and are returned back to publishers.\n\nSustained values above zero may indicate a routing problem on the publisher end.\n\n* [Unroutable Message Handling](https://www.rabbitmq.com/publishers.html#unroutable)\n* [When Will Published Messages Be Confirmed by the Broker?](https://www.rabbitmq.com/confirms.html#when-publishes-are-confirmed)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 0
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/rabbit/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#C4162A",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 36
},
"id": 16,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_unroutable_returned_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Unroutable messages returned to publishers / s",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 41
},
"id": 29,
"panels": [],
"title": "OUTGOING MESSAGES",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages delivered to consumers. It includes messages that have been redelivered.\n\nThis metric does not include messages that have been fetched by consumers using `basic.get` (consumed by polling).\n\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 42
},
"id": 14,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(\n (rate(rabbitmq_global_messages_delivered_consume_auto_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) +\n (rate(rabbitmq_global_messages_delivered_consume_manual_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"})\n) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages delivered / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages that have been redelivered to consumers. It includes messages that have been requeued automatically and redelivered due to channel exceptions or connection closures.\n\nHaving some redeliveries is expected, but if this metric is consistently non-zero, it is worth investigating why.\n\n* [Negative Acknowledgement and Requeuing of Deliveries](https://www.rabbitmq.com/confirms.html#consumer-nacks-requeue)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 20
},
{
"color": "red",
"value": 100
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 42
},
"id": 15,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_redelivered_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages redelivered / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of message deliveries to consumers that use manual acknowledgement mode.\n\nWhen this mode is used, RabbitMQ waits for consumers to acknowledge messages before more messages can be delivered.\n\nThis is the safest way of consuming messages.\n\n* [Consumer Acknowledgements](https://www.rabbitmq.com/confirms.html)\n* [Consumer Prefetch](https://www.rabbitmq.com/consumer-prefetch.html)\n* [Consumer Acknowledgement Modes, Prefetch and Throughput](https://www.rabbitmq.com/confirms.html#channel-qos-prefetch-throughput)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 47
},
"id": 20,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_delivered_consume_manual_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages delivered with manual ack / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of message deliveries to consumers that use automatic acknowledgement mode.\n\nWhen this mode is used, RabbitMQ does not wait for consumers to acknowledge message deliveries.\n\nThis mode is fire-and-forget and does not offer any delivery safety guarantees. It tends to provide higher throughput and it may lead to consumer overload and higher consumer memory usage.\n\n* [Consumer Acknowledgement Modes, Prefetch and Throughput](https://www.rabbitmq.com/confirms.html#channel-qos-prefetch-throughput)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 47
},
"id": 21,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_delivered_consume_auto_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages delivered auto ack / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of message acknowledgements coming from consumers that use manual acknowledgement mode.\n\n* [Consumer Acknowledgements](https://www.rabbitmq.com/confirms.html)\n* [Consumer Prefetch](https://www.rabbitmq.com/consumer-prefetch.html)\n* [Consumer Acknowledgement Modes, Prefetch and Throughput](https://www.rabbitmq.com/confirms.html#channel-qos-prefetch-throughput)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 52
},
"id": 22,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_acknowledged_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Messages acknowledged / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages delivered to polling consumers that use automatic acknowledgement mode.\n\nThe use of polling consumers is highly inefficient and therefore strongly discouraged.\n\n* [Fetching individual messages](https://www.rabbitmq.com/consumers.html#fetching)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 0
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/rabbit/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#C4162A",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 52
},
"id": 24,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_delivered_get_auto_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Polling operations with auto ack / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of polling consumer operations that yield no result.\n\nAny value above zero means that RabbitMQ resources are wasted by polling consumers.\n\nCompare this metric to the other polling consumer metrics to see the inefficiency rate.\n\nThe use of polling consumers is highly inefficient and therefore strongly discouraged.\n\n* [Fetching individual messages](https://www.rabbitmq.com/consumers.html#fetching)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 0
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/rabbit/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#C4162A",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 57
},
"id": 25,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_get_empty_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Polling operations that yield no result / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of messages delivered to polling consumers that use manual acknowledgement mode.\n\nThe use of polling consumers is highly inefficient and therefore strongly discouraged.\n\n* [Fetching individual messages](https://www.rabbitmq.com/consumers.html#fetching)\n* [Consumers](https://www.rabbitmq.com/consumers.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 0
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/rabbit/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#C4162A",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 12,
"y": 57
},
"id": 23,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_global_messages_delivered_get_manual_ack_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Polling operations with manual ack / s",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 62
},
"id": 53,
"panels": [],
"title": "QUEUES",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "Total number of queue masters per node. \n\nThis metric makes it easy to see sub-optimal queue distribution in a cluster.\n\n* [Queue Masters, Data Locality](https://www.rabbitmq.com/ha.html#master-migration-data-locality)\n* [Queues](https://www.rabbitmq.com/queues.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 63
},
"id": 57,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "rabbitmq_queues * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}",
"format": "time_series",
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Total queues",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of queue declarations performed by clients.\n\nLow sustained values above zero are to be expected. High rates may be indicative of queue churn or high rates of connection recovery. Confirm connection recovery rates by using the _Connections opened_ metric.\n\n* [Queues](https://www.rabbitmq.com/queues.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 4,
"x": 12,
"y": 63
},
"id": 58,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_queues_declared_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Queues declared / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of new queues created (as opposed to redeclarations).\n\nLow sustained values above zero are to be expected. High rates may be indicative of queue churn or high rates of connection recovery. Confirm connection recovery rates by using the _Connections opened_ metric.\n\n* [Queues](https://www.rabbitmq.com/queues.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 4,
"x": 16,
"y": 63
},
"id": 60,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_queues_created_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Queues created / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of queues deleted.\n\nLow sustained values above zero are to be expected. High rates may be indicative of queue churn or high rates of connection recovery. Confirm connection recovery rates by using the _Connections opened_ metric.\n\n* [Queues](https://www.rabbitmq.com/queues.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 4,
"x": 20,
"y": 63
},
"id": 59,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_queues_deleted_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Queues deleted / s",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 68
},
"id": 51,
"panels": [],
"title": "CHANNELS",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "Total number of channels on all currently opened connections.\n\nIf this metric grows monotonically it is highly likely a channel leak in one of the applications. Confirm channel leaks by using the _Channels opened_ and _Channels closed_ metrics.\n\n* [Channel Leak](https://www.rabbitmq.com/channels.html#channel-leaks)\n* [Channels](https://www.rabbitmq.com/channels.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 69
},
"id": 54,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "rabbitmq_channels * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Total channels",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of new channels opened by applications across all connections. Channels are expected to be long-lived.\n\nLow sustained values above zero are to be expected. High rates may be indicative of channel churn or mass connection recovery. Confirm connection recovery rates by using the _Connections opened_ metric.\n\n* [High Channel Churn](https://www.rabbitmq.com/channels.html#high-channel-churn)\n* [Channels](https://www.rabbitmq.com/channels.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 69
},
"id": 55,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_channels_opened_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Channels opened / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of channels closed by applications across all connections. Channels are expected to be long-lived.\n\nLow sustained values above zero are to be expected. High rates may be indicative of channel churn or mass connection recovery. Confirm connection recovery rates by using the _Connections opened_ metric.\n\n* [High Channel Churn](https://www.rabbitmq.com/channels.html#high-channel-churn)\n* [Channels](https://www.rabbitmq.com/channels.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 69
},
"id": 56,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_channels_closed_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Channels closed / s",
"type": "timeseries"
},
{
"collapsed": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 74
},
"id": 46,
"panels": [],
"title": "CONNECTIONS",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "Total number of client connections.\n\nIf this metric grows monotonically it is highly likely a connection leak in one of the applications. Confirm connection leaks by using the _Connections opened_ and _Connections closed_ metrics.\n\n* [Connection Leak](https://www.rabbitmq.com/connections.html#monitoring)\n* [Connections](https://www.rabbitmq.com/connections.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 12,
"x": 0,
"y": 75
},
"id": 47,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "rabbitmq_connections * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Total connections",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of new connections opened by clients. Connections are expected to be long-lived.\n\nLow sustained values above zero are to be expected. High rates may be indicative of connection churn or mass connection recovery.\n\n* [Connection Leak](https://www.rabbitmq.com/connections.html#monitoring)\n* [Connections](https://www.rabbitmq.com/connections.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 75
},
"id": 48,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_connections_opened_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Connections opened / s",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "The rate of connections closed. Connections are expected to be long-lived.\n\nLow sustained values above zero are to be expected. High rates may be indicative of connection churn or mass connection recovery.\n\n* [Connections](https://www.rabbitmq.com/connections.html)",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 100,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "line+area"
}
},
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "orange",
"value": 2
},
{
"color": "red",
"value": 10
}
]
},
"unit": "short"
},
"overrides": [
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?0(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#56A64B",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?1(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#F2CC0C",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?2(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#3274D9",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?3(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#A352CC",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?4(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FF780A",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?5(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#96D98D",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?6(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFEE52",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?7(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#8AB8FF",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?8(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#CA95E5",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "/^rabbit@[a-zA-Z\\.\\-]*?9(\\b|\\.)/"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "#FFB357",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 75
},
"id": 49,
"links": [],
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max",
"min"
],
"displayMode": "hidden",
"placement": "bottom"
},
"tooltip": {
"mode": "multi"
}
},
"pluginVersion": "8.3.4",
"targets": [
{
"expr": "sum(rate(rabbitmq_connections_closed_total[60s]) * on(instance, job) group_left(rabbitmq_cluster, rabbitmq_node) rabbitmq_identity_info{rabbitmq_cluster=\"$rabbitmq_cluster\", namespace=\"$namespace\"}) by(rabbitmq_node)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{rabbitmq_node}}",
"refId": "A"
}
],
"title": "Connections closed / s",
"type": "timeseries"
}
],
"refresh": "15s",
"schemaVersion": 34,
"style": "dark",
"tags": [
"rabbitmq-prometheus"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "default",
"value": "default"
},
"datasource": "PBFA97CFB590B2093",
"hide": 2,
"includeAll": false,
"label": "datasource",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values(rabbitmq_identity_info, namespace)",
"hide": 0,
"includeAll": false,
"label": "Namespace",
"multi": false,
"name": "namespace",
"options": [],
"query": {
"query": "label_values(rabbitmq_identity_info, namespace)",
"refId": "Prometheus-namespace-Variable-Query"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values(rabbitmq_identity_info{namespace=\"$namespace\"}, rabbitmq_cluster)",
"hide": 0,
"includeAll": false,
"label": "RabbitMQ Cluster",
"multi": false,
"name": "rabbitmq_cluster",
"options": [],
"query": {
"query": "label_values(rabbitmq_identity_info{namespace=\"$namespace\"}, rabbitmq_cluster)",
"refId": "Prometheus-rabbitmq_cluster-Variable-Query"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"tagValuesQuery": "",
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"15s",
"30s",
"1m",
"5m",
"10m"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "RabbitMQ-Overview",
"uid": "Kn5xm-gZk",
"version": 20220805,
"weekStart": "",
"gnetId": 10991
}
================================================
FILE: deployments/configs/grafana/provisioning/dashboards/dashboard.yml
================================================
# https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
apiVersion: 1
providers:
- name: "default"
orgId: 1
folder: ""
type: file
disableDeletion: false
editable: true
allowUiUpdates: true
updateIntervalSeconds: 5 # how often Grafana will scan for changed dashboards
options:
path: /var/lib/grafana/dashboards # path to dashboards on disk
================================================
FILE: deployments/configs/grafana/provisioning/datasources/datasource.yml
================================================
# https://grafana.com/docs/grafana/latest/administration/provisioning/
# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/grafana-datasources.yaml
# https://github.com/grafana/intro-to-mltp/blob/main/grafana/provisioning/datasources/datasources.yaml
apiVersion: 1
datasources:
# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/grafana-datasources.yaml
- name: Prometheus
type: prometheus
typeName: Prometheus
uid: prometheus-uid
access: proxy
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
readOnly: false
user: ''
database: ''
version: 1
editable: false
jsonData:
httpMethod: GET
- name: Jaeger
type: jaeger
access: proxy
url: http://jaeger-all-in-one:16686
editable: false
uid: jaeger-uid
- name: Zipkin
type: zipkin
access: proxy
url: http://zipkin-all-in-one:9411
editable: false
uid: zipkin-uid
# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/grafana-datasources.yaml
- name: Tempo
type: tempo
access: proxy
orgId: 1
url: http://tempo:3200
basicAuth: false
isDefault: false
version: 1
editable: false
apiVersion: 1
uid: tempo-uid
jsonData:
httpMethod: GET
serviceMap:
datasourceUid: prometheus-uid
streamingEnabled:
search: true
#https://github.com/grafana/intro-to-mltp/blob/main/grafana/provisioning/datasources/datasources.yaml
- name: Loki
type: loki
access: proxy
uid: loki-uid
url: http://loki:3100
user: ''
database: ''
readOnly: false
jsonData:
derivedFields:
- datasourceUid: tempo-uid
matcherRegex: "^.*?traceI[d|D]=(\\w+).*$"
name: traceId
url: '$${__value.raw}'
- name: Kibana
type: elasticsearch
url: http://elasticsearch:9200
access: proxy
isDefault: false
uid: kibana-uid
jsonData:
esVersion: 7
timeField: "@timestamp"
maxConcurrentShardRequests: 256
interval: Daily
logMessageField: "message" # Optional: Field for log messages
logLevelField: "level" # Optional: Field for log levels
editable: true
================================================
FILE: deployments/configs/loki-config.yaml
================================================
# https://grafana.com/docs/loki/latest/configure/examples/configuration-examples/
# https://github.com/grafana/loki/issues/2018#issuecomment-970789233
# https://grafana.com/docs/opentelemetry/collector/send-logs-to-loki/
# https://github.com/grafana/loki/blob/main/examples/getting-started/loki-config.yaml
# https://github.com/grafana/loki/blob/main/cmd/loki/loki-local-config.yaml
# https://grafana.com/docs/loki/latest/configure/examples/configuration-examples/#1-local-configuration-exampleyaml
---
# https://grafana.com/docs/loki/latest/configure/examples/configuration-examples/#1-local-configuration-exampleyaml
auth_enabled: false
# This is a complete configuration to deploy Loki backed by the filesystem.
# The index will be shipped to the storage via tsdb-shipper.
server:
http_listen_port: 3100
common:
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
schema_config:
configs:
- from: 2020-05-15
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
filesystem:
directory: /tmp/loki/chunks
# https://grafana.com/docs/loki/latest/send-data/otel/
# https://grafana.com/docs/loki/latest/send-data/otel/#changing-the-default-mapping-of-otlp-to-loki-format
limits_config:
# this attribute should be `true` when we use `otlphttp/loki`, but if we want to use `loki component` from `opentelemetry-collector-contrib` it should be false.
allow_structured_metadata: true
================================================
FILE: deployments/configs/otel-collector-config.yaml
================================================
# ref: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/otel-collector-config.yaml
# https://opentelemetry.io/docs/collector/configuration/
# https://opentelemetry.io/docs/collector/architecture/
# https://betterstack.com/community/guides/observability/opentelemetry-collector/
# https://signoz.io/blog/opentelemetry-collector-complete-guide/
# This configuration sets up an OpenTelemetry Collector that receives trace data via the OTLP protocol over HTTP on port 4318, applies batch processing, and then exports the processed traces
# to exporter components like `Jaeger` endpoint located at `jaeger-all-in-one:4317`. It also includes a health_check extension for monitoring the collector's status.
# Receivers in the OpenTelemetry Collector are components that collect telemetry data (traces, metrics, and logs) from various sources, such as instrumented applications or agents.
# They act as entry points, converting incoming data into OpenTelemetry's internal format for processing and export.
# https://betterstack.com/community/guides/observability/opentelemetry-collector/#exploring-the-opentelemetry-collector-components
# https://opentelemetry.io/docs/collector/architecture/#receivers
# https://github.com/open-telemetry/opentelemetry-collector/blob/main/receiver/README.md
# https://opentelemetry.io/docs/collector/configuration/#receivers
receivers:
# supported receivers
# https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver
# instead of specifying details explicitly we can just use `otlp` and it uses both grpc and http
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# prometheus:
# config:
# scrape_configs:
# - job_name: 'node-exporter'
# scrape_interval: 10s
# static_configs:
# - targets: [ 'node-exporter:9100' ]
# Processors in the OpenTelemetry Collector modify and enhance telemetry data by filtering, transforming, enriching, or batching it to prepare it for export.
# https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor
processors:
batch: # Batches logs for better performance
# - Exporters in the OpenTelemetry Collector send processed telemetry data to backend systems like observability platforms, databases, or cloud services for storage, visualization, and analysis.
# - The `key` follows the `type/name` format, where `type` specifies the exporter `type` (e.g., otlp, kafka, prometheus), and `name` (optional) can be appended to provide a unique name for multiple instance of the same type
# https://betterstack.com/community/guides/observability/opentelemetry-collector/#exploring-the-opentelemetry-collector-components
# https://opentelemetry.io/docs/collector/architecture/#exporters
# https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter
# https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter
# https://opentelemetry.io/docs/collector/configuration/#exporters
exporters:
# valid values: [prometheusremotewrite zipkin otlphttp file kafka prometheus debug nop otlp opencensus]
# Prometheus exporter metrics
prometheus:
endpoint: "0.0.0.0:8889"
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
# https://grafana.com/docs/loki/latest/send-data/otel/
# https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/lokiexporter/README.md
otlphttp/loki:
endpoint: "http://loki:3100/otlp"
tls:
insecure: true
# # we can also use `loki component` from `opentelemetry-collector-contrib` if we don't want to use builtin `otlphttp` exporter type and `http://loki:3100/otlp` loki endpoint
# loki:
# endpoint: "http://loki:3100/loki/api/v1/push"
# tls:
# insecure: true
debug:
# https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/elasticsearchexporter
# using `elasticsearch` from `opentelemetry-collector-contrib` components because it doesn't exist in `opentelemetry-collector`
elasticsearch:
endpoint: "http://elasticsearch:9200"
zipkin:
endpoint: "http://zipkin-all-in-one:9411/api/v2/spans"
format: proto
# export collected telemetry traces to jaeger OTLP grpc port, we can send data to other available endpoints and ports on jaeger as well
otlp/jaeger:
endpoint: "http://jaeger-all-in-one:4317"
tls:
insecure: true
otlp/tempo:
endpoint: "http://tempo:4317"
tls:
insecure: true
# seq-otlp:
# endpoint: "http://seq:5341/ingest/otlp"
# https://opentelemetry.io/docs/collector/configuration/#extensions
# https://github.com/open-telemetry/opentelemetry-collector/blob/main/extension/README.md
extensions:
pprof:
endpoint: 0.0.0.0:1888
zpages:
endpoint: 0.0.0.0:55679
health_check:
endpoint: 0.0.0.0:13133
# - The service section is used to configure what components are enabled in the Collector based on the configuration found in the receivers, processors, exporters, and extensions sections.
# - If a component is configured, but not defined within the service section, then it’s not enabled.
# https://betterstack.com/community/guides/observability/opentelemetry-collector/#exploring-the-opentelemetry-collector-components
# https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/README.md
# https://opentelemetry.io/docs/collector/architecture/
# https://opentelemetry.io/docs/collector/configuration/#service
service:
# The `service.extensions` subsection determines which of the configured extensions will be enabled
extensions: [pprof, zpages, health_check]
# The `service.pipeline` Each pipeline starts with one or more receivers collecting data, which is then processed sequentially by processors (applying transformations, filtering, or sampling).
# The processed data is finally sent to all configured exporters, ensuring each receives a copy. Components must be pre-configured in their respective sections before being used in a pipeline.
# pipeline activate predefined components, defined components are disabled by default
pipelines:
traces:
receivers: [otlp]
processors: [batch]
# https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection
exporters: [debug, zipkin, otlp/jaeger, otlp/tempo]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug, prometheusremotewrite, prometheus]
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/loki, elasticsearch]
================================================
FILE: deployments/configs/prometheus.yaml
================================================
# ref: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/prometheus.yaml
# https://prometheus.io/docs/introduction/first_steps/
# https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
global:
scrape_interval: 5s
scrape_configs:
# when we use otel-collector we should comment other jobs in prometheus config, and we read configs from `otel-collector-config`
- job_name: "otel-collector"
scrape_interval: 10s
static_configs:
# otel-collector Prometheus exporter metrics
- targets: [ 'otel-collector:8889' ]
- targets: [ 'otel-collector:8888' ]
- job_name: "prometheus"
static_configs:
- targets: ["prometheus:9090"]
# # https://prometheus.io/docs/guides/node-exporter/
# # https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
# - job_name: "node-exporter"
# static_configs:
# - targets: [ 'node-exporter:9100' ]
# # if we don't use otel collector we should uncomment this
# # scrap application metrics
# # http://localhost:4000/metrics by AddPrometheusExporter()
# - job_name: vertical-slice-template-api
# scrape_interval: 10s
# metrics_path: /metrics
# static_configs:
# - targets: ['host.docker.internal:4000']
#
# # if we don't use otel collector we should uncomment this
# # scrap application health metrics
# # http://localhost:4000/health/metrics by AddPrometheusExporter()
# - job_name: vertical-slice-template-api-healthchecks
# scrape_interval: 10s
# metrics_path: /health/metrics
# static_configs:
# - targets: ['host.docker.internal:4000']
## https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/prometheus.yaml
# - job_name: 'tempo'
# static_configs:
# - targets: [ 'tempo:3200' ]
================================================
FILE: deployments/configs/tempo.yaml
================================================
# https://grafana.com/docs/tempo/latest/configuration/
# https://github.com/grafana/tempo/blob/main/example/docker-compose/local/tempo.yaml
# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/tempo.yaml
stream_over_http_enabled: true
server:
http_listen_port: 3200
log_level: info
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "tempo:4317"
ingester:
max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally
compactor:
compaction:
block_retention: 1h # overall Tempo trace retention. set for demo purposes
metrics_generator:
registry:
external_labels:
source: tempo
cluster: docker-compose
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/write
send_exemplars: true
traces_storage:
path: /var/tempo/generator/traces
storage:
trace:
backend: local # backend configuration to use
wal:
path: /var/tempo/wal # where to store the wal locally
local:
path: /var/tempo/blocks
overrides:
defaults:
metrics_generator:
processors: [service-graphs, span-metrics, local-blocks] # enables metrics generator
generate_native_histograms: both
================================================
FILE: deployments/docker-compose/docker-compose.infrastructure.yaml
================================================
# ref: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/docker-compose.yaml
# ref: https://github.com/joaofbantunes/DotNetMicroservicesObservabilitySample/blob/main/docker-compose.yml
# ref: https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/docker-compose.yml
# https://github.com/grafana/intro-to-mltp
# https://stackoverflow.com/questions/65272764/ports-are-not-available-listen-tcp-0-0-0-0-50070-bind-an-attempt-was-made-to
name: booking-microservices-infrastructure
services:
#######################################################
# rabbitmq
#######################################################
rabbitmq:
image: rabbitmq:management
container_name: rabbitmq
restart: unless-stopped
ports:
- "5672:5672"
- "15672:15672"
# volumes:
# - rabbitmq:/var/lib/rabbitmq
networks:
- infrastructure
#######################################################
# postgres
#######################################################
postgres:
image: postgres:latest
container_name: postgres
restart: unless-stopped
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
command:
- "postgres"
- "-c"
- "wal_level=logical"
- "-c"
- "max_prepared_transactions=10"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- infrastructure
#######################################################
# EventStoreDB
#######################################################
eventstore:
container_name: eventstore
image: eventstore/eventstore:latest
restart: unless-stopped
environment:
- EVENTSTORE_CLUSTER_SIZE=1
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INSECURE=True
- EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=True
ports:
- "2113:2113"
networks:
- infrastructure
# #######################################################
# # Mongo
# #######################################################
mongo:
image: mongo:latest
container_name: mongo
restart: unless-stopped
# environment:
# - MONGO_INITDB_ROOT_USERNAME=root
# - MONGO_INITDB_ROOT_PASSWORD=secret
ports:
- 27017:27017
networks:
- infrastructure
# #######################################################
# # Redis
# #######################################################
redis:
image: redis
container_name: redis
restart: unless-stopped
ports:
- 6379:6379
networks:
- infrastructure
# #######################################################
# # jaeger
# # https://www.jaegertracing.io/docs/1.64/deployment/
# # https://www.jaegertracing.io/docs/1.6/deployment/
# #######################################################
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
container_name: jaeger-all-in-one
restart: unless-stopped
ports:
- "6831:6831/udp" # UDP port for Jaeger agent
- "16686:16686" # endpoints and Jaeger UI
- "14268:14268" # HTTP port for accept trace spans directly from clients
- "14317:4317" # OTLP gRPC receiver for jaeger
- "14318:4318" # OTLP http receiver for jaeger
# - "9411" # Accepts Zipkin spans - /api/v2/spans
networks:
- infrastructure
# #######################################################
# # zipkin
# # https://zipkin.io/pages/quickstart
# #######################################################
zipkin-all-in-one:
image: openzipkin/zipkin:latest
container_name: zipkin-all-in-one
restart: unless-stopped
ports:
- "9411:9411"
networks:
- infrastructure
# #######################################################
# # otel-collector
# # https://opentelemetry.io/docs/collector/installation/
# # https://github.com/open-telemetry/opentelemetry-collector
# # https://github.com/open-telemetry/opentelemetry-collector-contrib
# # we can use none contrib docker `otel/opentelemetry-collector` version from `https://github.com/open-telemetry/opentelemetry-collector` repository but,
# # if we need more components like `elasticsearch` we should use `otel/opentelemetry-collector-contrib` image of `https://github.com/open-telemetry/opentelemetry-collector-contrib` repository.
# #######################################################
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
restart: unless-stopped
command: ["--config=/etc/otelcol-contrib/config.yaml"]
volumes:
- ./../configs/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
ports:
- "11888:1888" # pprof extension
- "8888:8888" # Prometheus metrics exposed by the Collector
- "8889:8889" # Prometheus exporter metrics
- "13133:13133" # health_check extension
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP http receiver
- "55679:55679" # zpages extension
networks:
- infrastructure
# #######################################################
# # prometheus
# # https://prometheus.io/docs/introduction/first_steps/
# # https://prometheus.io/docs/prometheus/3.1/installation/
# # https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
# #######################################################
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./../configs/prometheus.yaml:/etc/prometheus/prometheus.yml
# to passe one flag, such as "--log.level=debug" or "--web.enable-remote-write-receiver", we need to override the whole command, as we can't just pass one extra argument
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
# need this for the OpenTelemetry collector to be able to put metrics into Prometheus
- "--web.enable-remote-write-receiver"
# - "--log.level=debug"
networks:
- infrastructure
# #######################################################
# # node-exporter
# # https://prometheus.io/docs/guides/node-exporter/
# # https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
# #######################################################
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
ports:
- "9101:9100"
networks:
- infrastructure
# #######################################################
# # grafana
# # https://grafana.com/docs/grafana/latest/administration/provisioning/
# # https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
# # https://grafana.com/docs/grafana/latest/setup-grafana/configure-docker/
# # https://github.com/grafana/intro-to-mltp/blob/main/grafana/provisioning/datasources/datasources.yaml
# #######################################################
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_FEATURE_TOGGLES_ENABLE=traceqlEditor
# - GF_AUTH_ANONYMOUS_ENABLED=true
# - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
# - GF_AUTH_DISABLE_LOGIN_FORM=true
depends_on:
- prometheus
ports:
- "3000:3000"
volumes:
- ./../configs/grafana/provisioning:/etc/grafana/provisioning
- ./../configs/grafana/dashboards:/var/lib/grafana/dashboards
## https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
# - ./../configs/grafana/grafana.ini:/etc/grafana/grafana.ini
networks:
- infrastructure
# #######################################################
# # tempo
# # https://github.com/grafana/tempo/blob/main/example/docker-compose/otel-collector/docker-compose.yaml
# # https://github.com/grafana/tempo/blob/main/example/docker-compose/shared
# # https://github.com/grafana/tempo/blob/main/example/docker-compose/local
# # https://github.com/grafana/tempo/tree/main/example/docker-compose
# #######################################################
tempo:
image: grafana/tempo:latest
container_name: tempo
restart: unless-stopped
command: [ "-config.file=/etc/tempo.yaml" ]
volumes:
- ./../configs/tempo.yaml:/etc/tempo.yaml
ports:
- "3200" # tempo
- "24317:4317" # otlp grpc
- "24318:4318" # otlp http
networks:
- infrastructure
# #######################################################
# # loki
# # https://grafana.com/docs/opentelemetry/collector/send-logs-to-loki/
# # https://github.com/grafana/loki/blob/main/production/docker-compose.yaml
# # https://github.com/grafana/loki/blob/main/examples/getting-started/docker-compose.yaml
# #######################################################
loki:
image: grafana/loki:latest
hostname: loki
container_name: loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- ./../configs/loki-config.yaml:/etc/loki/local-config.yaml
networks:
- infrastructure
# #######################################################
# # elasticsearch
# # https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docker.html#docker-compose-file
# #######################################################
elasticsearch:
container_name: elasticsearch
restart: unless-stopped
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
environment:
- discovery.type=single-node
- cluster.name=docker-cluster
- node.name=docker-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- network.host=0.0.0.0
- http.port=9200
- transport.host=localhost
- bootstrap.memory_lock=true
- cluster.routing.allocation.disk.threshold_enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elastic-data:/usr/share/elasticsearch/data
ports:
- ${ELASTIC_HOST_PORT:-9200}:${ELASTIC_PORT:-9200}
- 9300:9300
networks:
- infrastructure
# #######################################################
# # kibana
# # https://www.elastic.co/guide/en/kibana/current/docker.html
# #######################################################
kibana:
image: docker.elastic.co/kibana/kibana:8.17.0
container_name: kibana
restart: unless-stopped
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- ${KIBANA_HOST_PORT:-5601}:${KIBANA_PORT:-5601}
depends_on:
- elasticsearch
networks:
- infrastructure
# #######################################################
# # cadvisor
# #######################################################
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
devices:
- /dev/kmsg
networks:
- infrastructure
networks:
infrastructure:
name: infrastructure
driver: bridge
volumes:
elastic-data:
postgres-data:
================================================
FILE: deployments/docker-compose/docker-compose.yaml
================================================
name: booking-microservices
services:
#######################################################
# rabbitmq
#######################################################
rabbitmq:
image: rabbitmq:management
container_name: rabbitmq
restart: unless-stopped
ports:
- "5672:5672"
- "15672:15672"
# volumes:
# - rabbitmq:/var/lib/rabbitmq
networks:
- booking
#######################################################
# postgres
#######################################################
postgres:
image: postgres:latest
container_name: postgres
restart: unless-stopped
ports:
- '5432:5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
command:
- "postgres"
- "-c"
- "wal_level=logical"
- "-c"
- "max_prepared_transactions=10"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- booking
#######################################################
# EventStoreDB
#######################################################
eventstore:
container_name: eventstore
image: eventstore/eventstore:latest
restart: unless-stopped
environment:
- EVENTSTORE_CLUSTER_SIZE=1
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INSECURE=True
- EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=True
ports:
- "2113:2113"
networks:
- booking
#######################################################
# Mongo
#######################################################
mongo:
image: mongo:latest
container_name: mongo
restart: unless-stopped
# environment:
# - MONGO_INITDB_ROOT_USERNAME=root
# - MONGO_INITDB_ROOT_PASSWORD=secret
ports:
- 27017:27017
networks:
- booking
#######################################################
# Redis
#######################################################
redis:
image: redis
container_name: redis
restart: unless-stopped
ports:
- 6379:6379
networks:
- booking
#######################################################
# jaeger
# https://www.jaegertracing.io/docs/1.64/deployment/
# https://www.jaegertracing.io/docs/1.6/deployment/
#######################################################
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
container_name: jaeger-all-in-one
restart: unless-stopped
ports:
- "6831:6831/udp" # UDP port for Jaeger agent
- "16686:16686" # endpoints and Jaeger UI
- "14268:14268" # HTTP port for accept trace spans directly from clients
- "14317:4317" # OTLP gRPC receiver for jaeger
- "14318:4318" # OTLP http receiver for jaeger
# - "9411" # Accepts Zipkin spans - /api/v2/spans
networks:
- booking
#######################################################
# zipkin
# https://zipkin.io/pages/quickstart
#######################################################
zipkin-all-in-one:
image: openzipkin/zipkin:latest
container_name: zipkin-all-in-one
restart: unless-stopped
ports:
- "9411:9411"
networks:
- booking
#######################################################
# otel-collector
# https://opentelemetry.io/docs/collector/installation/
# https://github.com/open-telemetry/opentelemetry-collector
# https://github.com/open-telemetry/opentelemetry-collector-contrib
# we can use none contrib docker `otel/opentelemetry-collector` version from `https://github.com/open-telemetry/opentelemetry-collector` repository but,
# if we need more components like `elasticsearch` we should use `otel/opentelemetry-collector-contrib` image of `https://github.com/open-telemetry/opentelemetry-collector-contrib` repository.
#######################################################
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
restart: unless-stopped
command: ["--config=/etc/otelcol-contrib/config.yaml"]
volumes:
- ./../configs/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
ports:
- "11888:1888" # pprof extension
- "8888:8888" # Prometheus metrics exposed by the Collector
- "8889:8889" # Prometheus exporter metrics
- "13133:13133" # health_check extension
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP http receiver
- "55679:55679" # zpages extension
networks:
- booking
#######################################################
# prometheus
# https://prometheus.io/docs/introduction/first_steps/
# https://prometheus.io/docs/prometheus/3.1/installation/
# https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
#######################################################
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./../configs/prometheus.yaml:/etc/prometheus/prometheus.yml
# to passe one flag, such as "--log.level=debug" or "--web.enable-remote-write-receiver", we need to override the whole command, as we can't just pass one extra argument
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
# need this for the OpenTelemetry collector to be able to put metrics into Prometheus
- "--web.enable-remote-write-receiver"
# - "--log.level=debug"
networks:
- booking
#######################################################
# node-exporter
# https://prometheus.io/docs/guides/node-exporter/
# https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-prometheus/prometheus-config-examples/docker-compose-linux/
#######################################################
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
ports:
- "9101:9100"
networks:
- booking
#######################################################
# grafana
# https://grafana.com/docs/grafana/latest/administration/provisioning/
# https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
# https://grafana.com/docs/grafana/latest/setup-grafana/configure-docker/
# https://github.com/grafana/intro-to-mltp/blob/main/grafana/provisioning/datasources/datasources.yaml
#######################################################
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
environment:
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_FEATURE_TOGGLES_ENABLE=traceqlEditor
# - GF_AUTH_ANONYMOUS_ENABLED=true
# - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
# - GF_AUTH_DISABLE_LOGIN_FORM=true
depends_on:
- prometheus
ports:
- "3000:3000"
volumes:
- ./../configs/grafana/provisioning:/etc/grafana/provisioning
- ./../configs/grafana/dashboards:/var/lib/grafana/dashboards
## https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
# - ./../configs/grafana/grafana.ini:/etc/grafana/grafana.ini
networks:
- booking
#######################################################
# tempo
# https://github.com/grafana/tempo/blob/main/example/docker-compose/otel-collector/docker-compose.yaml
# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared
# https://github.com/grafana/tempo/blob/main/example/docker-compose/local
# https://github.com/grafana/tempo/tree/main/example/docker-compose
#######################################################
tempo:
image: grafana/tempo:latest
container_name: tempo
restart: unless-stopped
command: [ "-config.file=/etc/tempo.yaml" ]
volumes:
- ./../configs/tempo.yaml:/etc/tempo.yaml
ports:
- "3200" # tempo
- "24317:4317" # otlp grpc
- "24318:4318" # otlp http
networks:
- booking
#######################################################
# loki
# https://grafana.com/docs/opentelemetry/collector/send-logs-to-loki/
# https://github.com/grafana/loki/blob/main/production/docker-compose.yaml
# https://github.com/grafana/loki/blob/main/examples/getting-started/docker-compose.yaml
#######################################################
loki:
image: grafana/loki:latest
hostname: loki
container_name: loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- ./../configs/loki-config.yaml:/etc/loki/local-config.yaml
networks:
- booking
#######################################################
# elasticsearch
# https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docker.html#docker-compose-file
#######################################################
elasticsearch:
container_name: elasticsearch
restart: unless-stopped
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
environment:
- discovery.type=single-node
- cluster.name=docker-cluster
- node.name=docker-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- network.host=0.0.0.0
- http.port=9200
- transport.host=localhost
- bootstrap.memory_lock=true
- cluster.routing.allocation.disk.threshold_enabled=false
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- elastic-data:/usr/share/elasticsearch/data
ports:
- ${ELASTIC_HOST_PORT:-9200}:${ELASTIC_PORT:-9200}
- 9300:9300
networks:
- booking
#######################################################
# kibana
# https://www.elastic.co/guide/en/kibana/current/docker.html
#######################################################
kibana:
image: docker.elastic.co/kibana/kibana:8.17.0
container_name: kibana
restart: unless-stopped
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- ${KIBANA_HOST_PORT:-5601}:${KIBANA_PORT:-5601}
depends_on:
- elasticsearch
networks:
- booking
#######################################################
# cadvisor
#######################################################
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
devices:
- /dev/kmsg
networks:
- booking
######################################################
# Api-Gateway
######################################################
api-gateway:
image: api-gateway
build:
args:
Version: "1"
context: ../../
dockerfile: src/ApiGateway/Dockerfile
container_name: api-gateway
ports:
- "5001:80"
- "5000:443"
volumes:
- ~/.aspnet/https:/https:ro
environment:
- ASPNETCORE_ENVIRONMENT=docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5000
- ASPNETCORE_HTTP_PORT=5001
- ASPNETCORE_Kestrel__Certificates__Default__Password=password
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
networks:
- booking
#######################################################
# Flight
#######################################################
flight:
image: flight
build:
args:
Version: "1"
context: ../../
dockerfile: src/Services/Flight/Dockerfile
container_name: flight
ports:
- 5004:80
- 5003:443
volumes:
- ~/.aspnet/https:/https:ro
environment:
- ASPNETCORE_ENVIRONMENT=docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5003
- ASPNETCORE_HTTP_PORT=5004
- ASPNETCORE_Kestrel__Certificates__Default__Password=password
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
networks:
- booking
#######################################################
# Identity
#######################################################
identity:
image: identity
build:
args:
Version: "1"
context: ../../
dockerfile: src/Services/Identity/Dockerfile
container_name: identity
ports:
- 6005:80
- 5005:443
volumes:
- ~/.aspnet/https:/https:ro
environment:
- ASPNETCORE_ENVIRONMENT=docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5005
- ASPNETCORE_HTTP_PORT=6005
- ASPNETCORE_Kestrel__Certificates__Default__Password=password
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
networks:
- booking
#######################################################
# Passenger
#######################################################
passenger:
image: passenger
build:
args:
Version: "1"
context: ../../
dockerfile: src/Services/Passenger/Dockerfile
container_name: passenger
ports:
- 6012:80
- 5012:443
volumes:
- ~/.aspnet/https:/https:ro
environment:
- ASPNETCORE_ENVIRONMENT=docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5012
- ASPNETCORE_HTTP_PORT=6012
- ASPNETCORE_Kestrel__Certificates__Default__Password=password
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
networks:
- booking
#######################################################
# Booking
#######################################################
booking:
image: booking
build:
args:
Version: "1"
context: ../../
dockerfile: src/Services/Booking/Dockerfile
container_name: booking
ports:
- 6010:80
- 5010:443
volumes:
- ~/.aspnet/https:/https:ro
environment:
- ASPNETCORE_ENVIRONMENT=docker
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5010
- ASPNETCORE_HTTP_PORT=6010
- ASPNETCORE_Kestrel__Certificates__Default__Password=password
- ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx
networks:
- booking
networks:
booking:
name: booking
driver: bridge
volumes:
elastic-data:
postgres-data:
================================================
FILE: deployments/kubernetes/booking-cert-manager.yml
================================================
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# Staging API
server: https://acme-staging-v02.api.letsencrypt.org/directory
# server: https://acme-v02.api.letsencrypt.org/directory
email: test@email.com
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
================================================
FILE: deployments/kubernetes/booking-microservices.yml
================================================
## ref: https://kompose.io
#######################################################
# Network
#######################################################
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
creationTimestamp: null
name: booking
spec:
ingress:
- from:
- podSelector:
matchLabels:
io.kompose.network/booking: "true"
podSelector:
matchLabels:
io.kompose.network/booking: "true"
---
#######################################################
# ElasticSearch
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: elasticsearch
spec:
replicas: 1
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
containers:
- name: elasticsearch
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
env:
- name: discovery.type
value: "single-node"
- name: cluster.name
value: "docker-cluster"
- name: node.name
value: "docker-node"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
- name: xpack.security.enabled
value: "false"
- name: xpack.security.http.ssl.enabled
value: "false"
- name: xpack.security.transport.ssl.enabled
value: "false"
- name: network.host
value: "0.0.0.0"
- name: http.port
value: "9200"
- name: transport.host
value: "localhost"
- name: bootstrap.memory_lock
value: "true"
- name: cluster.routing.allocation.disk.threshold_enabled
value: "false"
ports:
- containerPort: 9200
- containerPort: 9300
volumeMounts:
- mountPath: /usr/share/elasticsearch/data
name: elastic-data
volumes:
- name: elastic-data
persistentVolumeClaim:
claimName: elasticsearch-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: elasticsearch-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: elasticsearch
spec:
selector:
app: elasticsearch
ports:
- port: 9200
targetPort: 9200
- port: 9300
targetPort: 9300
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: elasticsearch-pv
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
#######################################################
# Kibana
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana
spec:
replicas: 1
selector:
matchLabels:
app: kibana
template:
metadata:
labels:
app: kibana
spec:
containers:
- name: kibana
image: docker.elastic.co/kibana/kibana:8.17.0
env:
- name: ELASTICSEARCH_HOSTS
value: "http://elasticsearch:9200"
ports:
- containerPort: 5601
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: kibana
spec:
selector:
app: kibana
ports:
- port: 5601
targetPort: 5601
type: ClusterIP
---
#######################################################
# Tempo
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: tempo
spec:
replicas: 1
selector:
matchLabels:
app: tempo
template:
metadata:
labels:
app: tempo
spec:
containers:
- name: tempo
image: grafana/tempo:latest
args:
- "-config.file=/etc/tempo.yaml"
ports:
- containerPort: 3200
- containerPort: 4317
- containerPort: 4318
volumeMounts:
- mountPath: /etc/tempo.yaml
name: tempo-config
subPath: tempo.yaml
volumes:
- name: tempo-config
configMap:
name: tempo-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: tempo-config
data:
tempo.yaml: |
# Your Tempo configuration here
---
apiVersion: v1
kind: Service
metadata:
name: tempo
spec:
selector:
app: tempo
ports:
- port: 3200
targetPort: 3200
- port: 4317
targetPort: 4317
- port: 4318
targetPort: 4318
type: ClusterIP
---
#######################################################
# Looki
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
spec:
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
containers:
- name: loki
image: grafana/loki:latest
args:
- "-config.file=/etc/loki/local-config.yaml"
ports:
- containerPort: 3100
volumeMounts:
- mountPath: /etc/loki/local-config.yaml
name: loki-config
subPath: local-config.yaml
volumes:
- name: loki-config
configMap:
name: loki-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
data:
local-config.yaml: |
# Your Loki configuration here
---
apiVersion: v1
kind: Service
metadata:
name: loki
spec:
selector:
app: loki
ports:
- port: 3100
targetPort: 3100
type: ClusterIP
---
#######################################################
# Event Store
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: eventstore
spec:
replicas: 1
selector:
matchLabels:
app: eventstore
template:
metadata:
labels:
app: eventstore
spec:
containers:
- name: eventstore
image: eventstore/eventstore:latest
env:
- name: EVENTSTORE_CLUSTER_SIZE
value: "1"
- name: EVENTSTORE_RUN_PROJECTIONS
value: "All"
- name: EVENTSTORE_START_STANDARD_PROJECTIONS
value: "True"
- name: EVENTSTORE_HTTP_PORT
value: "2113"
- name: EVENTSTORE_INSECURE
value: "True"
- name: EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP
value: "True"
ports:
- containerPort: 2113
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: eventstore
spec:
selector:
app: eventstore
ports:
- port: 2113
targetPort: 2113
type: ClusterIP
---
#######################################################
# Jaeger
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
spec:
replicas: 1
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:latest
ports:
- containerPort: 6831
protocol: UDP
- containerPort: 16686
- containerPort: 14268
- containerPort: 4317
- containerPort: 4318
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: jaeger
spec:
selector:
app: jaeger
ports:
- port: 6831
targetPort: 6831
protocol: UDP
- port: 16686
targetPort: 16686
- port: 14268
targetPort: 14268
- port: 4317
targetPort: 4317
- port: 4318
targetPort: 4318
type: ClusterIP
---
#######################################################
# Zipkin
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: zipkin
spec:
replicas: 1
selector:
matchLabels:
app: zipkin
template:
metadata:
labels:
app: zipkin
spec:
containers:
- name: zipkin
image: openzipkin/zipkin:latest
ports:
- containerPort: 9411
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: zipkin
spec:
selector:
app: zipkin
ports:
- port: 9411
targetPort: 9411
type: ClusterIP
---
#######################################################
# OpenTelemetry Collector
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector
spec:
replicas: 1
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:latest
args: ["--config=/etc/otelcol-contrib/config.yaml"]
ports:
- containerPort: 11888
- containerPort: 8888
- containerPort: 8889
- containerPort: 13133
- containerPort: 4317
- containerPort: 4318
- containerPort: 55679
volumeMounts:
- mountPath: /etc/otelcol-contrib/config.yaml
name: otel-config
subPath: config.yaml
volumes:
- name: otel-config
configMap:
name: otel-collector-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
data:
config.yaml: |
# Your OpenTelemetry Collector configuration here
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
spec:
selector:
app: otel-collector
ports:
- port: 11888
targetPort: 11888
- port: 8888
targetPort: 8888
- port: 8889
targetPort: 8889
- port: 13133
targetPort: 13133
- port: 4317
targetPort: 4317
- port: 4318
targetPort: 4318
- port: 55679
targetPort: 55679
type: ClusterIP
---
#######################################################
# Prometheus
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:latest
args:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
- "--web.enable-remote-write-receiver"
ports:
- containerPort: 9090
volumeMounts:
- mountPath: /etc/prometheus/prometheus.yml
name: prometheus-config
subPath: prometheus.yml
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus.yml: |
# Your Prometheus configuration here
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
spec:
selector:
app: prometheus
ports:
- port: 9090
targetPort: 9090
type: ClusterIP
---
#######################################################
# Grafana
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
containers:
- name: grafana
image: grafana/grafana:latest
env:
- name: GF_INSTALL_PLUGINS
value: "grafana-clock-panel,grafana-simple-json-datasource"
- name: GF_SECURITY_ADMIN_USER
value: "admin"
- name: GF_SECURITY_ADMIN_PASSWORD
value: "admin"
- name: GF_FEATURE_TOGGLES_ENABLE
value: "traceqlEditor"
ports:
- containerPort: 3000
volumeMounts:
- mountPath: /etc/grafana/provisioning
name: grafana-provisioning
- mountPath: /var/lib/grafana/dashboards
name: grafana-dashboards
volumes:
- name: grafana-provisioning
configMap:
name: grafana-provisioning
- name: grafana-dashboards
configMap:
name: grafana-dashboards
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-provisioning
data:
# Your Grafana provisioning configuration here
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboards
data:
# Your Grafana dashboards configuration here
---
apiVersion: v1
kind: Service
metadata:
name: grafana
spec:
selector:
app: grafana
ports:
- port: 3000
targetPort: 3000
type: ClusterIP
---
#######################################################
# Node Exporter
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-exporter
spec:
replicas: 1
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:latest
args:
- "--path.procfs=/host/proc"
- "--path.rootfs=/rootfs"
- "--path.sysfs=/host/sys"
ports:
- containerPort: 9100
volumeMounts:
- mountPath: /host/proc
name: proc
readOnly: true
- mountPath: /host/sys
name: sys
readOnly: true
- mountPath: /rootfs
name: rootfs
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
- name: rootfs
hostPath:
path: /
---
apiVersion: v1
kind: Service
metadata:
name: node-exporter
spec:
selector:
app: node-exporter
ports:
- port: 9100
targetPort: 9100
type: ClusterIP
---
#######################################################
# Cadvisor
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: cadvisor
spec:
replicas: 1
selector:
matchLabels:
app: cadvisor
template:
metadata:
labels:
app: cadvisor
spec:
containers:
- name: cadvisor
image: gcr.io/cadvisor/cadvisor:latest
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /rootfs
name: rootfs
readOnly: true
- mountPath: /var/run
name: var-run
readOnly: true
- mountPath: /sys
name: sys
readOnly: true
- mountPath: /var/lib/docker
name: var-lib-docker
readOnly: true
- mountPath: /dev/disk
name: dev-disk
readOnly: true
volumes:
- name: rootfs
hostPath:
path: /
- name: var-run
hostPath:
path: /var/run
- name: sys
hostPath:
path: /sys
- name: var-lib-docker
hostPath:
path: /var/lib/docker
- name: dev-disk
hostPath:
path: /dev/disk
---
apiVersion: v1
kind: Service
metadata:
name: cadvisor
spec:
selector:
app: cadvisor
ports:
- port: 8080
targetPort: 8080
type: ClusterIP
---
#######################################################
# Mongo
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:latest
ports:
- containerPort: 27017
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: mongo
spec:
selector:
app: mongo
ports:
- port: 27017
targetPort: 27017
type: ClusterIP
---
#######################################################
# Postgres
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:latest
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: postgres
ports:
- containerPort: 5432
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
---
#######################################################
# Rabbitmq
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:management
ports:
- containerPort: 5672
- containerPort: 15672
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
spec:
selector:
app: rabbitmq
ports:
- port: 5672
targetPort: 5672
- port: 15672
targetPort: 15672
type: ClusterIP
---
#######################################################
# Redis
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
type: ClusterIP
---
#######################################################
# ConfigMap AppSettings
#######################################################
apiVersion: v1
kind: ConfigMap
metadata:
name: appsettings
data:
appsettings.json: |-
{
.Files.Get "settings/appsettings.docker.json"
}
#ref: https://www.mrjamiebowman.com/software-development/dotnet/kubernetes-configmaps-with-net-core/
---
#######################################################
# Flight
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: flight-deployment
labels:
app: flight
spec:
replicas: 1
selector:
matchLabels:
app: flight
template:
metadata:
labels:
app: flight
spec:
containers:
- image: meysamh66/booking-microservices-flight:v1.6.7
name: flight
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: docker
- name: ASPNETCORE_URLS
value: http://+
volumeMounts:
- name: appsettings-volume
mountPath: /app/Settings
volumes:
- name: appsettings-volume
configMap:
name: appsettings
---
apiVersion: v1
kind: Service
metadata:
name: flight
spec:
selector:
app: flight
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
#######################################################
# Identity
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: identity-deployment
labels:
app: identity
spec:
replicas: 1
selector:
matchLabels:
app: identity
template:
metadata:
labels:
app: identity
spec:
containers:
- image: meysamh66/booking-microservices-identity:v1.6.7
name: identity
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: docker
- name: ASPNETCORE_URLS
value: http://+
volumeMounts:
- name: appsettings-volume
mountPath: /app/Settings
volumes:
- name: appsettings-volume
configMap:
name: appsettings
---
apiVersion: v1
kind: Service
metadata:
name: identity
spec:
selector:
app: identity
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
#######################################################
# Booking
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: booking-deployment
labels:
app: booking
spec:
replicas: 1
selector:
matchLabels:
app: booking
template:
metadata:
labels:
app: booking
spec:
containers:
- image: meysamh66/booking-microservices-booking:v1.6.7
name: booking
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: docker
- name: ASPNETCORE_URLS
value: http://+
volumeMounts:
- name: appsettings-volume
mountPath: /app/Settings
volumes:
- name: appsettings-volume
configMap:
name: appsettings
---
apiVersion: v1
kind: Service
metadata:
name: booking
spec:
selector:
app: booking
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
#######################################################
# Passenger
#######################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: passenger-deployment
labels:
app: passenger
spec:
replicas: 1
selector:
matchLabels:
app: passenger
template:
metadata:
labels:
app: passenger
spec:
containers:
- image: meysamh66/booking-microservices-passenger:v1.6.7
name: passenger
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: docker
- name: ASPNETCORE_URLS
value: http://+
volumeMounts:
- name: appsettings-volume
mountPath: /app/Settings
volumes:
- name: appsettings-volume
configMap:
name: appsettings
---
#######################################################
# Ingress Controller
#######################################################
apiVersion: v1
kind: Service
metadata:
name: passenger
spec:
selector:
app: passenger
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: booking-microservies
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
cert-manager.io/cluster-issuer: "letsencrypt-staging"
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
nginx.ingress.kubernetes.io/proxy-buffers: "4 256k"
nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "256k"
nginx.ingress.kubernetes.io/client-header-buffer-size: "64k"
nginx.ingress.kubernetes.io/http2-max-field-size: "16k"
nginx.ingress.kubernetes.io/http2-max-header-size: "128k"
nginx.ingress.kubernetes.io/large-client-header-buffers: "8 64k"
spec:
ingressClassName: nginx
tls:
- hosts:
- booking-microservices.com
secretName: letsencrypt-staging
rules:
- host: booking-microservices.com
http:
paths:
- path: /identity
pathType: Prefix
backend:
service:
name: flight
port:
number: 80
- path: /identity/(.*)
pathType: Prefix
backend:
service:
name: identity
port:
number: 80
- path: /flight
pathType: Prefix
backend:
service:
name: flight
port:
number: 80
- path: /flight/(.*)
pathType: Prefix
backend:
service:
name: flight
port:
number: 80
- path: /passenger
pathType: Prefix
backend:
service:
name: passenger
port:
number: 80
- path: /passenger/(.*)
pathType: Prefix
backend:
service:
name: passenger
port:
number: 80
- path: /booking
pathType: Prefix
backend:
service:
name: booking
port:
number: 80
- path: /booking/(.*)
pathType: Prefix
backend:
service:
name: booking
port:
number: 80
================================================
FILE: global.json
================================================
{
"sdk": {
"version": "10.0.103",
"rollForward": "latestFeature"
}
}
================================================
FILE: package.json
================================================
{
"name": "booking-microservices",
"version": "1.0.0",
"description": "booking-microservices",
"author": "Meysam Hadeli",
"license": "MIT",
"main": "index.js",
"scripts": {
"prepare": "husky && dotnet tool restore",
"format": "dotnet tool run dotnet-csharpier booking-microservices.sln",
"ci-format": "dotnet tool run dotnet-csharpier booking-microservices.sln --check",
"upgrade-packages": "dotnet outdated --upgrade"
},
"devDependencies": {
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"husky": "^9.1.6"
}
}
================================================
FILE: scripts/setup_kubectl_gitpod.sh
================================================
#!/bin/bash
# Download kubectl binary
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -L -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
# Make the kubectl binary executable
chmod +x kubectl
# Move the binary in to PATH
sudo mv kubectl /usr/local/bin/
================================================
FILE: src/ApiGateway/Dockerfile
================================================
# ---------- Build Stage ----------
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy solution-level files
COPY .editorconfig .
COPY global.json .
COPY Directory.Build.props .
# Copy project files first (better Docker caching)
COPY src/BuildingBlocks/BuildingBlocks.csproj src/BuildingBlocks/
COPY src/ApiGateway/src/ApiGateway.csproj src/ApiGateway/src/
COPY src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj src/Aspire/src/ServiceDefaults/
# Restore dependencies
RUN dotnet restore src/ApiGateway/src/ApiGateway.csproj
# Copy the rest of the source code
COPY src ./src
# Publish (build included)
RUN dotnet publish src/ApiGateway/src/ApiGateway.csproj \
-c Release \
-o /app/publish \
--no-restore
# ---------- Runtime Stage ----------
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:80
ENV ASPNETCORE_ENVIRONMENT=docker
EXPOSE 80
ENTRYPOINT ["dotnet", "ApiGateway.dll"]
================================================
FILE: src/ApiGateway/src/ApiGateway.csproj
================================================
================================================
FILE: src/ApiGateway/src/Program.cs
================================================
using BuildingBlocks.Web;
using Figgle;
using Figgle.Fonts;
var builder = WebApplication.CreateBuilder(args);
var env = builder.Environment;
var appOptions = builder.Services.GetOptions("AppOptions");
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
builder.Services.AddControllers();
builder.Services.AddHttpContextAccessor();
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("Yarp"));
var app = builder.Build();
app.UseCorrelationId();
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapReverseProxy();
});
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
app.Run();
================================================
FILE: src/ApiGateway/src/Properties/launchSettings.json
================================================
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"ApiGateway": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:5000;http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
================================================
FILE: src/ApiGateway/src/appsettings.Development.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: src/ApiGateway/src/appsettings.docker.json
================================================
{
"LogOptions": {
"Level": "Information",
"LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}",
"ElasticUri": "elasticsearch:9200"
},
"Yarp": {
"clusters": {
"flight": {
"destinations": {
"destination1": {
"address": "http://flight:80"
}
}
},
"identity": {
"destinations": {
"destination1": {
"address": "http://identity:80"
}
}
},
"passenger": {
"destinations": {
"destination1": {
"address": "http://passenger:80"
}
}
},
"booking": {
"destinations": {
"destination1": {
"address": "http://booking:80"
}
}
}
}
}
}
================================================
FILE: src/ApiGateway/src/appsettings.json
================================================
{
"AppOptions": {
"Name": "ApiGateway"
},
"LogOptions": {
"Level": "Information",
"LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}",
"ElasticUri": "http://localhost:9200"
},
"HealthOptions": {
"Enabled": false
},
"Yarp": {
"routes": {
"identity": {
"clusterId": "identity",
"match": {
"path": "identity/{**catch-all}"
},
"Transforms": [
{
"PathRemovePrefix": "identity"
}
]
},
"flight": {
"clusterId": "flight",
"match": {
"path": "flight/{**catch-all}"
},
"Transforms": [
{
"PathRemovePrefix": "flight"
}
]
},
"passenger": {
"clusterId": "passenger",
"match": {
"path": "passenger/{**catch-all}"
},
"Transforms": [
{
"PathRemovePrefix": "passenger"
}
]
},
"booking": {
"clusterId": "booking",
"match": {
"path": "booking/{**catch-all}"
},
"Transforms": [
{
"PathRemovePrefix": "booking"
}
]
}
},
"clusters": {
"flight": {
"destinations": {
"destination1": {
"address": "http://localhost:5004"
}
}
},
"identity": {
"destinations": {
"destination1": {
"address": "http://localhost:6005"
}
}
},
"passenger": {
"destinations": {
"destination1": {
"address": "http://localhost:6012"
}
}
},
"booking": {
"destinations": {
"destination1": {
"address": "http://localhost:6010"
}
}
}
}
},
"AllowedHosts": "*"
}
================================================
FILE: src/Aspire/src/AppHost/AppHost.csproj
================================================
Exe
net10.0
enable
enable
true
bde28db3-85ba-4201-b889-0f3faba24169
================================================
FILE: src/Aspire/src/AppHost/Program.cs
================================================
using System.Net.Sockets;
var builder = DistributedApplication.CreateBuilder(args);
builder.AddDockerComposeEnvironment("docker-compose");
// 1. Database Services
var pgUsername = builder.AddParameter("pg-username", "postgres", secret: true);
var pgPassword = builder.AddParameter("pg-password", "postgres", secret: true);
var postgres = builder.AddPostgres("postgres", pgUsername, pgPassword)
.WithImage("postgres:latest")
.WithEndpoint(
"tcp",
e =>
{
e.Port = 5432;
e.TargetPort = 5432;
e.IsProxied = true;
e.IsExternal = false;
})
.WithArgs(
"-c",
"wal_level=logical",
"-c",
"max_prepared_transactions=10");
if (builder.ExecutionContext.IsPublishMode)
{
postgres.WithDataVolume("postgres-data")
.WithLifetime(ContainerLifetime.Persistent);
}
var flightDb = postgres.AddDatabase("flight");
var passengerDb = postgres.AddDatabase("passenger");
var identityDb = postgres.AddDatabase("identity");
var persistMessageDb = postgres.AddDatabase("persist-message");
var mongoUsername = builder.AddParameter("mongo-username", "root", secret: true);
var mongoPassword = builder.AddParameter("mongo-password", "secret", secret: true);
var mongo = builder.AddMongoDB("mongo", userName: mongoUsername, password: mongoPassword)
.WithImage("mongo")
.WithImageTag("latest")
.WithEndpoint(
"tcp",
e =>
{
e.Port = 27017;
e.TargetPort = 27017;
e.IsProxied = true;
e.IsExternal = false;
});
if (builder.ExecutionContext.IsPublishMode)
{
mongo.WithDataVolume("mongo-data")
.WithLifetime(ContainerLifetime.Persistent);
}
var redis = builder.AddRedis("redis")
.WithImage("redis:latest")
.WithEndpoint(
"tcp",
e =>
{
e.Port = 6379;
e.TargetPort = 6379;
e.IsProxied = true;
e.IsExternal = false;
});
if (builder.ExecutionContext.IsPublishMode)
{
redis.WithDataVolume("redis-data")
.WithLifetime(ContainerLifetime.Persistent);
}
var eventstore = builder.AddEventStore("eventstore")
.WithImage("eventstore/eventstore")
.WithEnvironment("EVENTSTORE_CLUSTER_SIZE", "1")
.WithEnvironment("EVENTSTORE_RUN_PROJECTIONS", "All")
.WithEnvironment("EVENTSTORE_START_STANDARD_PROJECTIONS", "True")
.WithEnvironment("EVENTSTORE_INSECURE", "True")
.WithEnvironment("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "True")
.WithEndpoint(
"http",
e =>
{
e.TargetPort = 2113;
e.Port = 2113;
e.IsProxied = true;
e.IsExternal = true;
})
.WithEndpoint(
port: 1113,
targetPort: 1113,
name: "tcp",
isProxied: true,
isExternal: false);
if (builder.ExecutionContext.IsPublishMode)
{
eventstore.WithDataVolume("eventstore-data")
.WithLifetime(ContainerLifetime.Persistent);
}
// 2. Messaging Services
var rabbitmqUsername = builder.AddParameter("rabbitmq-username", "guest", secret: true);
var rabbitmqPassword = builder.AddParameter("rabbitmq-password", "guest", secret: true);
var rabbitmq = builder.AddRabbitMQ("rabbitmq", rabbitmqUsername, rabbitmqPassword)
.WithManagementPlugin()
.WithEndpoint(
"tcp",
e =>
{
e.TargetPort = 5672;
e.Port = 5672;
e.IsProxied = true;
e.IsExternal = false;
})
.WithEndpoint(
"management",
e =>
{
e.TargetPort = 15672;
e.Port = 15672;
e.IsProxied = true;
e.IsExternal = true;
});
if (builder.ExecutionContext.IsPublishMode)
{
rabbitmq.WithLifetime(ContainerLifetime.Persistent);
}
// // 3. Observability Services
var jaeger = builder.AddContainer("jaeger-all-in-one", "jaegertracing/all-in-one")
.WithEndpoint(
port: 6831,
targetPort: 6831,
name: "agent",
protocol: ProtocolType.Udp,
isProxied: true,
isExternal: false)
.WithEndpoint(port: 16686, targetPort: 16686, name: "http", isProxied: true, isExternal: true)
.WithEndpoint(port: 14268, targetPort: 14268, name: "collector", isProxied: true, isExternal: false)
.WithEndpoint(port: 14317, targetPort: 4317, name: "otlp-grpc", isProxied: true, isExternal: false)
.WithEndpoint(port: 14318, targetPort: 4318, name: "otlp-http", isProxied: true, isExternal: false);
if (builder.ExecutionContext.IsPublishMode)
{
jaeger.WithLifetime(ContainerLifetime.Persistent);
}
var zipkin = builder.AddContainer("zipkin-all-in-one", "openzipkin/zipkin")
.WithEndpoint(port: 9411, targetPort: 9411, name: "http", isProxied: true, isExternal: true);
if (builder.ExecutionContext.IsPublishMode)
{
zipkin.WithLifetime(ContainerLifetime.Persistent);
}
var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector-contrib")
.WithBindMount(
"../../../../deployments/configs/otel-collector-config.yaml",
"/etc/otelcol-contrib/config.yaml",
isReadOnly: true)
.WithArgs("--config=/etc/otelcol-contrib/config.yaml")
.WithEndpoint(port: 11888, targetPort: 1888, name: "otel-pprof", isProxied: true, isExternal: true)
.WithEndpoint(port: 8888, targetPort: 8888, name: "otel-metrics", isProxied: true, isExternal: true)
.WithEndpoint(port: 8889, targetPort: 8889, name: "otel-exporter-metrics", isProxied: true, isExternal: true)
.WithEndpoint(port: 13133, targetPort: 13133, name: "otel-health", isProxied: true, isExternal: true)
.WithEndpoint(port: 4317, targetPort: 4317, name: "otel-grpc", isProxied: true, isExternal: true)
.WithEndpoint(port: 4318, targetPort: 4318, name: "otel-http", isProxied: true, isExternal: true)
.WithEndpoint(port: 55679, targetPort: 55679, name: "otel-zpages", isProxied: true, isExternal: true);
if (builder.ExecutionContext.IsPublishMode)
{
otelCollector.WithLifetime(ContainerLifetime.Persistent);
}
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("../../../../deployments/configs/prometheus.yaml", "/etc/prometheus/prometheus.yml")
.WithArgs(
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--web.console.libraries=/usr/share/prometheus/console_libraries",
"--web.console.templates=/usr/share/prometheus/consoles",
"--web.enable-remote-write-receiver")
.WithEndpoint(port: 9090, targetPort: 9090, name: "http", isProxied: true, isExternal: true);
if (builder.ExecutionContext.IsPublishMode)
{
prometheus.WithLifetime(ContainerLifetime.Persistent);
}
var grafana = builder.AddContainer("grafana", "grafana/grafana")
.WithEnvironment("GF_INSTALL_PLUGINS", "grafana-clock-panel,grafana-simple-json-datasource")
.WithEnvironment("GF_SECURITY_ADMIN_USER", "admin")
.WithEnvironment("GF_SECURITY_ADMIN_PASSWORD", "admin")
.WithEnvironment("GF_FEATURE_TOGGLES_ENABLE", "traceqlEditor")
.WithBindMount("../../../../deployments/configs/grafana/provisioning", "/etc/grafana/provisioning")
.WithBindMount("../../../../deployments/configs/grafana/dashboards", "/var/lib/grafana/dashboards")
.WithEndpoint(port: 3000, targetPort: 3000, name: "http", isProxied: true, isExternal: true);
if (builder.ExecutionContext.IsPublishMode)
{
grafana.WithLifetime(ContainerLifetime.Persistent);
}
var nodeExporter = builder.AddContainer("node-exporter", "prom/node-exporter")
.WithBindMount("/proc", "/host/proc", isReadOnly: true)
.WithBindMount("/sys", "/host/sys", isReadOnly: true)
.WithBindMount("/", "/rootfs", isReadOnly: true)
.WithArgs(
"--path.procfs=/host/proc",
"--path.rootfs=/rootfs",
"--path.sysfs=/host/sys")
.WithEndpoint(port: 9101, targetPort: 9100, name: "http", isProxied: true, isExternal: true);
if (builder.ExecutionContext.IsPublishMode)
{
nodeExporter.WithLifetime(ContainerLifetime.Persistent);
}
var tempo = builder.AddContainer("tempo", "grafana/tempo")
.WithBindMount("../../../../deployments/configs/tempo.yaml", "/etc/tempo.yaml", isReadOnly: true)
.WithArgs("--config.file=/etc/tempo.yaml")
.WithEndpoint(port: 3200, targetPort: 3200, name: "http", isProxied: true, isExternal: false)
.WithEndpoint(port: 9095, targetPort: 9095, name: "grpc", isProxied: true, isExternal: false)
.WithEndpoint(port: 4317, targetPort: 4317, name: "otlp-grpc", isProxied: true, isExternal: false)
.WithEndpoint(port: 4318, targetPort: 4318, name: "otlp-http", isProxied: true, isExternal: false);
if (builder.ExecutionContext.IsPublishMode)
{
tempo.WithLifetime(ContainerLifetime.Persistent);
}
var loki = builder.AddContainer("loki", "grafana/loki")
.WithBindMount("../../../../deployments/configs/loki-config.yaml", "/etc/loki/local-config.yaml", isReadOnly: true)
.WithArgs("-config.file=/etc/loki/local-config.yaml")
.WithEndpoint(port: 3100, targetPort: 3100, name: "http", isProxied: true, isExternal: false)
.WithEndpoint(port: 9096, targetPort: 9096, name: "grpc", isProxied: true, isExternal: false);
if (builder.ExecutionContext.IsPublishMode)
{
loki.WithLifetime(ContainerLifetime.Persistent);
}
var elasticsearch = builder.AddElasticsearch("elasticsearch")
.WithImage("docker.elastic.co/elasticsearch/elasticsearch:8.17.0")
.WithEnvironment("discovery.type", "single-node")
.WithEnvironment("cluster.name", "docker-cluster")
.WithEnvironment("node.name", "docker-node")
.WithEnvironment("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
.WithEnvironment("xpack.security.enabled", "false")
.WithEnvironment("xpack.security.http.ssl.enabled", "false")
.WithEnvironment("xpack.security.transport.ssl.enabled", "false")
.WithEnvironment("network.host", "0.0.0.0")
.WithEnvironment("http.port", "9200")
.WithEnvironment("transport.host", "localhost")
.WithEnvironment("bootstrap.memory_lock", "true")
.WithEnvironment("cluster.routing.allocation.disk.threshold_enabled", "false")
.WithEndpoint(
"http",
e =>
{
e.TargetPort = 9200;
e.Port = 9200;
e.IsProxied = true;
e.IsExternal = false;
})
.WithEndpoint(
"internal",
e =>
{
e.TargetPort = 9300;
e.Port = 9300;
e.IsProxied = true;
e.IsExternal = false;
})
.WithDataVolume("elastic-data");
if (builder.ExecutionContext.IsPublishMode)
{
elasticsearch.WithLifetime(ContainerLifetime.Persistent);
}
var kibana = builder.AddContainer("kibana", "docker.elastic.co/kibana/kibana:8.17.0")
.WithEnvironment("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200")
.WithEndpoint(port: 5601, targetPort: 5601, name: "http", isProxied: true, isExternal: true)
.WithReference(elasticsearch)
.WaitFor(elasticsearch);
if (builder.ExecutionContext.IsPublishMode)
{
kibana.WithLifetime(ContainerLifetime.Persistent);
}
// 5. Application Services
var identity = builder.AddProject("identity-service")
.WithReference(persistMessageDb)
.WaitFor(persistMessageDb)
.WithReference(identityDb)
.WaitFor(identityDb)
.WithReference(mongo)
.WaitFor(mongo)
.WithReference(rabbitmq)
.WaitFor(rabbitmq)
.WithHttpEndpoint(port: 6005, name: "identity-http")
.WithHttpsEndpoint(port: 5005, name: "identity-https");
var passenger = builder.AddProject("passenger-service")
.WithReference(persistMessageDb)
.WaitFor(persistMessageDb)
.WithReference(passengerDb)
.WaitFor(passengerDb)
.WithReference(mongo)
.WaitFor(mongo)
.WithReference(rabbitmq)
.WaitFor(rabbitmq)
.WithHttpEndpoint(port: 6012, name: "passenger-http")
.WithHttpsEndpoint(port: 5012, name: "passenger-https");
var flight = builder.AddProject("flight-service")
.WithReference(persistMessageDb)
.WaitFor(persistMessageDb)
.WithReference(flightDb)
.WaitFor(flightDb)
.WithReference(mongo)
.WaitFor(mongo)
.WithReference(rabbitmq)
.WaitFor(rabbitmq)
.WithHttpEndpoint(port: 5004, name: "flight-http")
.WithHttpsEndpoint(port: 5003, name: "flight-https");
var booking = builder.AddProject("booking-service")
.WithReference(persistMessageDb)
.WaitFor(persistMessageDb)
.WithReference(eventstore)
.WaitFor(eventstore)
.WithReference(mongo)
.WaitFor(mongo)
.WithReference(rabbitmq)
.WaitFor(rabbitmq)
.WithHttpEndpoint(port: 6010, name: "booking-http")
.WithHttpsEndpoint(port: 5010, name: "booking-https");
var gateway = builder.AddProject("api-gateway")
.WithReference(flight)
.WaitFor(flight)
.WithReference(passenger)
.WaitFor(passenger)
.WithReference(identity)
.WaitFor(identity)
.WithReference(booking)
.WaitFor(booking)
.WithHttpEndpoint(port: 5001, name: "gateway-http")
.WithHttpsEndpoint(port: 5000, name: "gateway-https");
builder.Build().Run();
================================================
FILE: src/Aspire/src/AppHost/Properties/launchSettings.json
================================================
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"AppHost": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:18888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://otel-collector:4317",
"ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://otel-collector:4318"
}
}
}
}
================================================
FILE: src/Aspire/src/AppHost/appsettings.Development.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
================================================
FILE: src/Aspire/src/AppHost/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
================================================
FILE: src/Aspire/src/ServiceDefaults/Extensions.cs
================================================
using BuildingBlocks.HealthCheck;
using BuildingBlocks.OpenTelemetryCollector;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ServiceDefaults;
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this WebApplicationBuilder builder)
{
builder.Services.AddCustomHealthCheck();
builder.AddCustomObservability();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler(options =>
{
var timeSpan = TimeSpan.FromMinutes(1);
options.CircuitBreaker.SamplingDuration = timeSpan * 2;
options.TotalRequestTimeout.Timeout = timeSpan * 3;
options.Retry.MaxRetryAttempts = 3;
});
// Turn on service discovery by default
http.AddServiceDiscovery();
});
return builder;
}
public static WebApplication UseServiceDefaults(this WebApplication app)
{
app.UseCustomHealthCheck();
app.UseCustomObservability();
return app;
}
}
================================================
FILE: src/Aspire/src/ServiceDefaults/ServiceDefaults.csproj
================================================
net10.0
enable
enable
================================================
FILE: src/BuildingBlocks/BuildingBlocks.csproj
================================================
all
runtime; build; native; contentfiles; analyzers; buildtransitive
================================================
FILE: src/BuildingBlocks/Caching/CachingBehavior.cs
================================================
using EasyCaching.Core;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.Caching;
public class CachingBehavior : IPipelineBehavior
where TRequest : notnull, IRequest
where TResponse : notnull
{
private readonly IEasyCachingProvider _cachingProvider;
private readonly ILogger> _logger;
private readonly int defaultCacheExpirationInHours = 1;
public CachingBehavior(IEasyCachingProviderFactory cachingFactory,
ILogger> logger)
{
_logger = logger;
_cachingProvider = cachingFactory.GetCachingProvider("mem");
}
public async Task Handle(TRequest request, RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
if (request is not ICacheRequest cacheRequest)
// No cache request found, so just continue through the pipeline
return await next();
var cacheKey = cacheRequest.CacheKey;
var cachedResponse = await _cachingProvider.GetAsync(cacheKey);
if (cachedResponse.Value != null)
{
_logger.LogDebug("Response retrieved {TRequest} from cache. CacheKey: {CacheKey}",
typeof(TRequest).FullName, cacheKey);
return cachedResponse.Value;
}
var response = await next();
var expirationTime = cacheRequest.AbsoluteExpirationRelativeToNow ??
DateTime.Now.AddHours(defaultCacheExpirationInHours);
await _cachingProvider.SetAsync(cacheKey, response, expirationTime.TimeOfDay);
_logger.LogDebug("Caching response for {TRequest} with cache key: {CacheKey}", typeof(TRequest).FullName,
cacheKey);
return response;
}
}
================================================
FILE: src/BuildingBlocks/Caching/ICacheRequest.cs
================================================
namespace BuildingBlocks.Caching;
public interface ICacheRequest
{
string CacheKey { get; }
DateTime? AbsoluteExpirationRelativeToNow { get; }
}
================================================
FILE: src/BuildingBlocks/Caching/IInvalidateCacheRequest.cs
================================================
namespace BuildingBlocks.Caching
{
public interface IInvalidateCacheRequest
{
string CacheKey { get; }
}
}
================================================
FILE: src/BuildingBlocks/Caching/InvalidateCachingBehavior.cs
================================================
using EasyCaching.Core;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.Caching
{
public class InvalidateCachingBehavior : IPipelineBehavior
where TRequest : notnull, IRequest
where TResponse : notnull
{
private readonly ILogger> _logger;
private readonly IEasyCachingProvider _cachingProvider;
public InvalidateCachingBehavior(IEasyCachingProviderFactory cachingFactory,
ILogger> logger)
{
_logger = logger;
_cachingProvider = cachingFactory.GetCachingProvider("mem");
}
public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
{
if (request is not IInvalidateCacheRequest invalidateCacheRequest)
{
// No cache request found, so just continue through the pipeline
return await next();
}
var cacheKey = invalidateCacheRequest.CacheKey;
var response = await next();
await _cachingProvider.RemoveAsync(cacheKey);
_logger.LogDebug("Cache data with cache key: {CacheKey} removed.", cacheKey);
return response;
}
}
}
================================================
FILE: src/BuildingBlocks/Constants/IdentityConstant.cs
================================================
namespace BuildingBlocks.Constants;
public static class IdentityConstant
{
public static class Role
{
public const string Admin = "admin";
public const string User = "user";
}
}
================================================
FILE: src/BuildingBlocks/Contracts/EventBus.Messages/FlighContracts.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Contracts.EventBus.Messages;
public record FlightCreated(Guid Id) : IIntegrationEvent;
public record FlightUpdated(Guid Id) : IIntegrationEvent;
public record FlightDeleted(Guid Id) : IIntegrationEvent;
public record AircraftCreated(Guid Id) : IIntegrationEvent;
public record AirportCreated(Guid Id) : IIntegrationEvent;
public record SeatCreated(Guid Id) : IIntegrationEvent;
public record SeatReserved(Guid Id) : IIntegrationEvent;
================================================
FILE: src/BuildingBlocks/Contracts/EventBus.Messages/IdentityContracts.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Contracts.EventBus.Messages;
public record UserCreated(Guid Id, string Name, string PassportNumber) : IIntegrationEvent;
================================================
FILE: src/BuildingBlocks/Contracts/EventBus.Messages/PassengerContracts.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Contracts.EventBus.Messages;
public record PassengerRegistrationCompleted(Guid Id) : IIntegrationEvent;
public record PassengerCreated(Guid Id) : IIntegrationEvent;
================================================
FILE: src/BuildingBlocks/Contracts/EventBus.Messages/ReservationContracts.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Contracts.EventBus.Messages;
public record BookingCreated(Guid Id) : IIntegrationEvent;
================================================
FILE: src/BuildingBlocks/Core/CQRS/ICommand.cs
================================================
using MediatR;
namespace BuildingBlocks.Core.CQRS;
public interface ICommand : ICommand
{
}
public interface ICommand : IRequest
where T : notnull
{
}
================================================
FILE: src/BuildingBlocks/Core/CQRS/ICommandHandler.cs
================================================
using MediatR;
namespace BuildingBlocks.Core.CQRS;
public interface ICommandHandler : ICommandHandler
where TCommand : ICommand
{
}
public interface ICommandHandler : IRequestHandler
where TCommand : ICommand
where TResponse : notnull
{
}
================================================
FILE: src/BuildingBlocks/Core/CQRS/IQuery.cs
================================================
using MediatR;
namespace BuildingBlocks.Core.CQRS;
public interface IQuery : IRequest
where T : notnull
{
}
================================================
FILE: src/BuildingBlocks/Core/CQRS/IQueryHandler.cs
================================================
using MediatR;
namespace BuildingBlocks.Core.CQRS;
public interface IQueryHandler : IRequestHandler
where TQuery : IQuery
where TResponse : notnull
{
}
================================================
FILE: src/BuildingBlocks/Core/CompositeEventMapper.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core;
public class CompositeEventMapper : IEventMapper
{
private readonly IEnumerable _mappers;
public CompositeEventMapper(IEnumerable mappers)
{
_mappers = mappers;
}
public IIntegrationEvent? MapToIntegrationEvent(IDomainEvent @event)
{
foreach (var mapper in _mappers)
{
var integrationEvent = mapper.MapToIntegrationEvent(@event);
if (integrationEvent is not null)
return integrationEvent;
}
return null;
}
public IInternalCommand? MapToInternalCommand(IDomainEvent @event)
{
foreach (var mapper in _mappers)
{
var internalCommand = mapper.MapToInternalCommand(@event);
if (internalCommand is not null)
return internalCommand;
}
return null;
}
}
================================================
FILE: src/BuildingBlocks/Core/Event/EventType.cs
================================================
namespace BuildingBlocks.Core.Event;
[Flags]
public enum EventType
{
DomainEvent = 1,
IntegrationEvent = 2,
InternalCommand = 4
}
================================================
FILE: src/BuildingBlocks/Core/Event/IDomainEvent.cs
================================================
namespace BuildingBlocks.Core.Event;
public interface IDomainEvent : IEvent
{
}
================================================
FILE: src/BuildingBlocks/Core/Event/IEvent.cs
================================================
using MediatR;
namespace BuildingBlocks.Core.Event;
using global::MassTransit;
public interface IEvent : INotification
{
Guid EventId => NewId.NextGuid();
public DateTime OccurredOn => DateTime.Now;
public string EventType => GetType().AssemblyQualifiedName;
}
================================================
FILE: src/BuildingBlocks/Core/Event/IHaveIntegrationEvent.cs
================================================
namespace BuildingBlocks.Core.Event;
public interface IHaveIntegrationEvent
{
}
================================================
FILE: src/BuildingBlocks/Core/Event/IIntegrationEvent.cs
================================================
using MassTransit;
namespace BuildingBlocks.Core.Event;
[ExcludeFromTopology]
public interface IIntegrationEvent : IEvent
{
}
================================================
FILE: src/BuildingBlocks/Core/Event/IInternalCommand.cs
================================================
namespace BuildingBlocks.Core.Event;
public interface IInternalCommand : IEvent
{
}
================================================
FILE: src/BuildingBlocks/Core/Event/InternalCommand.cs
================================================
using BuildingBlocks.Core.CQRS;
namespace BuildingBlocks.Core.Event;
public record InternalCommand : IInternalCommand, ICommand;
================================================
FILE: src/BuildingBlocks/Core/Event/MessageEnvelope.cs
================================================
using Google.Protobuf;
namespace BuildingBlocks.Core.Event;
public class MessageEnvelope
{
public MessageEnvelope(object? message, IDictionary? headers = null)
{
Message = message;
Headers = headers ?? new Dictionary();
}
public object? Message { get; init; }
public IDictionary Headers { get; init; }
}
public class MessageEnvelope : MessageEnvelope
where TMessage : class, IMessage
{
public MessageEnvelope(TMessage message, IDictionary header) : base(message, header)
{
Message = message;
}
public new TMessage? Message { get; }
}
================================================
FILE: src/BuildingBlocks/Core/EventDispatcher.cs
================================================
using System.Security.Claims;
using BuildingBlocks.Core.Event;
using BuildingBlocks.PersistMessageProcessor;
using BuildingBlocks.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MessageEnvelope = BuildingBlocks.Core.Event.MessageEnvelope;
namespace BuildingBlocks.Core;
public sealed class EventDispatcher(
IServiceScopeFactory serviceScopeFactory,
IEventMapper eventMapper,
ILogger logger,
IPersistMessageProcessor persistMessageProcessor,
IHttpContextAccessor httpContextAccessor
)
: IEventDispatcher
{
public async Task SendAsync(IReadOnlyList events, Type type = null,
CancellationToken cancellationToken = default)
where T : IEvent
{
if (events.Count > 0)
{
var eventType = type != null && type.IsAssignableTo(typeof(IInternalCommand))
? EventType.InternalCommand
: EventType.DomainEvent;
async Task PublishIntegrationEvent(IReadOnlyList integrationEvents)
{
foreach (var integrationEvent in integrationEvents)
{
await persistMessageProcessor.PublishMessageAsync(
new MessageEnvelope(integrationEvent, SetHeaders()),
cancellationToken);
}
}
switch (events)
{
case IReadOnlyList domainEvents:
{
var integrationEvents = await MapDomainEventToIntegrationEventAsync(domainEvents)
.ConfigureAwait(false);
await PublishIntegrationEvent(integrationEvents);
break;
}
case IReadOnlyList integrationEvents:
await PublishIntegrationEvent(integrationEvents);
break;
}
if (type != null && eventType == EventType.InternalCommand)
{
var internalMessages = await MapDomainEventToInternalCommandAsync(events as IReadOnlyList)
.ConfigureAwait(false);
foreach (var internalMessage in internalMessages)
{
await persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken);
}
}
}
}
public async Task SendAsync(T @event, Type type = null,
CancellationToken cancellationToken = default)
where T : IEvent =>
await SendAsync(new[] { @event }, type, cancellationToken);
private Task> MapDomainEventToIntegrationEventAsync(
IReadOnlyList events)
{
logger.LogTrace("Processing integration events start...");
var wrappedIntegrationEvents = GetWrappedIntegrationEvents(events.ToList())?.ToList();
if (wrappedIntegrationEvents?.Count > 0)
return Task.FromResult>(wrappedIntegrationEvents);
var integrationEvents = new List();
using var scope = serviceScopeFactory.CreateScope();
foreach (var @event in events)
{
var eventType = @event.GetType();
logger.LogTrace($"Handling domain event: {eventType.Name}");
var integrationEvent = eventMapper.MapToIntegrationEvent(@event);
if (integrationEvent is null)
continue;
integrationEvents.Add(integrationEvent);
}
logger.LogTrace("Processing integration events done...");
return Task.FromResult>(integrationEvents);
}
private Task> MapDomainEventToInternalCommandAsync(
IReadOnlyList events)
{
logger.LogTrace("Processing internal message start...");
var internalCommands = new List();
using var scope = serviceScopeFactory.CreateScope();
foreach (var @event in events)
{
var eventType = @event.GetType();
logger.LogTrace($"Handling domain event: {eventType.Name}");
var integrationEvent = eventMapper.MapToInternalCommand(@event);
if (integrationEvent is null)
continue;
internalCommands.Add(integrationEvent);
}
logger.LogTrace("Processing internal message done...");
return Task.FromResult>(internalCommands);
}
private IEnumerable GetWrappedIntegrationEvents(IReadOnlyList domainEvents)
{
foreach (var domainEvent in domainEvents.Where(x =>
x is IHaveIntegrationEvent))
{
var genericType = typeof(IntegrationEventWrapper<>)
.MakeGenericType(domainEvent.GetType());
var domainNotificationEvent = (IIntegrationEvent)Activator
.CreateInstance(genericType, domainEvent);
yield return domainNotificationEvent;
}
}
private IDictionary SetHeaders()
{
var headers = new Dictionary();
headers.Add("CorrelationId", httpContextAccessor?.HttpContext?.GetCorrelationId());
headers.Add("UserId", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier));
headers.Add("UserName", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.Name));
return headers;
}
}
================================================
FILE: src/BuildingBlocks/Core/IEventDispatcher.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core;
public interface IEventDispatcher
{
public Task SendAsync(IReadOnlyList events, Type type = null, CancellationToken cancellationToken = default)
where T : IEvent;
public Task SendAsync(T @event, Type type = null, CancellationToken cancellationToken = default)
where T : IEvent;
}
================================================
FILE: src/BuildingBlocks/Core/IEventMapper.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core;
public interface IEventMapper
{
IIntegrationEvent? MapToIntegrationEvent(IDomainEvent @event);
IInternalCommand? MapToInternalCommand(IDomainEvent @event);
}
================================================
FILE: src/BuildingBlocks/Core/IntegrationEventWrapper.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core;
public record IntegrationEventWrapper(TDomainEventType DomainEvent) : IIntegrationEvent
where TDomainEventType : IDomainEvent;
================================================
FILE: src/BuildingBlocks/Core/Model/Aggregate.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core.Model;
public abstract record Aggregate : Entity, IAggregate
{
private readonly List _domainEvents = new();
public IReadOnlyList DomainEvents => _domainEvents.AsReadOnly();
public void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public IEvent[] ClearDomainEvents()
{
IEvent[] dequeuedEvents = _domainEvents.ToArray();
_domainEvents.Clear();
return dequeuedEvents;
}
}
================================================
FILE: src/BuildingBlocks/Core/Model/Entity.cs
================================================
namespace BuildingBlocks.Core.Model;
public abstract record Entity : IEntity
{
public T Id { get; set; }
public DateTime? CreatedAt { get; set; }
public long? CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public long? LastModifiedBy { get; set; }
public bool IsDeleted { get; set; }
public long Version { get; set; }
}
================================================
FILE: src/BuildingBlocks/Core/Model/IAggregate.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.Core.Model;
public interface IAggregate : IAggregate, IEntity
{
}
public interface IAggregate : IEntity
{
IReadOnlyList DomainEvents { get; }
IEvent[] ClearDomainEvents();
}
================================================
FILE: src/BuildingBlocks/Core/Model/IEntity.cs
================================================
namespace BuildingBlocks.Core.Model;
public interface IEntity : IEntity
{
public T Id { get; set; }
}
public interface IEntity : IVersion
{
public DateTime? CreatedAt { get; set; }
public long? CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public long? LastModifiedBy { get; set; }
public bool IsDeleted { get; set; }
}
================================================
FILE: src/BuildingBlocks/Core/Model/IVersion.cs
================================================
namespace BuildingBlocks.Core.Model;
// For handling optimistic concurrency
public interface IVersion
{
long Version { get; set; }
}
================================================
FILE: src/BuildingBlocks/Core/Pagination/Extensions.cs
================================================
namespace BuildingBlocks.Core.Pagination;
using Sieve.Models;
using Sieve.Services;
public static class Extensions
{
public static async Task> ApplyPagingAsync(
this IQueryable queryable,
IPageRequest pageRequest,
ISieveProcessor sieveProcessor,
CancellationToken cancellationToken = default
)
where TEntity : class
{
var sieveModel = new SieveModel
{
PageSize = pageRequest.PageSize,
Page = pageRequest.PageNumber,
Sorts = pageRequest.SortOrder,
Filters = pageRequest.Filters
};
// https://github.com/Biarity/Sieve/issues/34#issuecomment-403817573
var result = sieveProcessor.Apply(sieveModel, queryable, applyPagination: false);
var total = result.Count();
result = sieveProcessor.Apply(sieveModel, queryable, applyFiltering: false,
applySorting: false); // Only applies pagination
var items = await result
.ToAsyncEnumerable()
.ToListAsync(cancellationToken: cancellationToken);
return PageList.Create(items.AsReadOnly(), pageRequest.PageNumber, pageRequest.PageSize, total);
}
}
================================================
FILE: src/BuildingBlocks/Core/Pagination/IPageList.cs
================================================
namespace BuildingBlocks.Core.Pagination;
public interface IPageList
where T : class
{
int CurrentPageSize { get; }
int CurrentStartIndex { get; }
int CurrentEndIndex { get; }
int TotalPages { get; }
bool HasPrevious { get; }
bool HasNext { get; }
IReadOnlyList Items { get; init; }
int TotalCount { get; init; }
int PageNumber { get; init; }
int PageSize { get; init; }
}
================================================
FILE: src/BuildingBlocks/Core/Pagination/IPageQuery.cs
================================================
namespace BuildingBlocks.Core.Pagination;
using MediatR;
public interface IPageQuery : IPageRequest, IRequest
where TResponse : class
{ }
================================================
FILE: src/BuildingBlocks/Core/Pagination/IPageRequest.cs
================================================
namespace BuildingBlocks.Core.Pagination;
public interface IPageRequest
{
int PageNumber { get; init; }
int PageSize { get; init; }
string? Filters { get; init; }
string? SortOrder { get; init; }
}
================================================
FILE: src/BuildingBlocks/Core/Pagination/PageList.cs
================================================
namespace BuildingBlocks.Core.Pagination;
public record PageList(IReadOnlyList Items, int PageNumber, int PageSize, int TotalCount) : IPageList
where T : class
{
public int CurrentPageSize => Items.Count;
public int CurrentStartIndex => TotalCount == 0 ? 0 : ((PageNumber - 1) * PageSize) + 1;
public int CurrentEndIndex => TotalCount == 0 ? 0 : CurrentStartIndex + CurrentPageSize - 1;
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
public bool HasPrevious => PageNumber > 1;
public bool HasNext => PageNumber < TotalPages;
public static PageList Empty => new(Enumerable.Empty().ToList(), 0, 0, 0);
public static PageList Create(IReadOnlyList items, int pageNumber, int pageSize, int totalItems)
{
return new PageList(items, pageNumber, pageSize, totalItems);
}
}
================================================
FILE: src/BuildingBlocks/EFCore/AppDbContextBase.cs
================================================
using System.Collections.Immutable;
using BuildingBlocks.Core.Event;
using BuildingBlocks.Core.Model;
using BuildingBlocks.Web;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
using IsolationLevel = System.Data.IsolationLevel;
namespace BuildingBlocks.EFCore;
public abstract class AppDbContextBase : DbContext, IDbContext
{
private readonly ICurrentUserProvider? _currentUserProvider;
private readonly ILogger? _logger;
private IDbContextTransaction _currentTransaction;
protected AppDbContextBase(DbContextOptions options, ICurrentUserProvider? currentUserProvider = null, ILogger? logger = null) :
base(options)
{
_currentUserProvider = currentUserProvider;
_logger = logger;
}
protected override void OnModelCreating(ModelBuilder builder)
{
}
public IExecutionStrategy CreateExecutionStrategy() => Database.CreateExecutionStrategy();
public async Task BeginTransactionAsync(CancellationToken cancellationToken = default)
{
if (_currentTransaction != null)
return;
_currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
}
public async Task CommitTransactionAsync(CancellationToken cancellationToken = default)
{
try
{
await SaveChangesAsync(cancellationToken);
await _currentTransaction?.CommitAsync(cancellationToken)!;
}
catch
{
await RollbackTransactionAsync(cancellationToken);
throw;
}
finally
{
_currentTransaction?.Dispose();
_currentTransaction = null;
}
}
public async Task RollbackTransactionAsync(CancellationToken cancellationToken = default)
{
try
{
await _currentTransaction?.RollbackAsync(cancellationToken)!;
}
finally
{
_currentTransaction?.Dispose();
_currentTransaction = null;
}
}
//ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions
public Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default)
{
var strategy = CreateExecutionStrategy();
return strategy.ExecuteAsync(async () =>
{
await using var transaction =
await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
try
{
await SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
});
}
public override async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
OnBeforeSaving();
try
{
return await base.SaveChangesAsync(cancellationToken);
}
//ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations#resolving-concurrency-conflicts
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
if (databaseValues == null)
{
_logger.LogError("The record no longer exists in the database, The record has been deleted by another user.");
throw;
}
// Refresh the original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
return await base.SaveChangesAsync(cancellationToken);
}
}
public IReadOnlyList GetDomainEvents()
{
var domainEntities = ChangeTracker
.Entries()
.Where(x => x.Entity.DomainEvents.Any())
.Select(x => x.Entity)
.ToList();
var domainEvents = domainEntities
.SelectMany(x => x.DomainEvents)
.ToImmutableList();
domainEntities.ForEach(entity => entity.ClearDomainEvents());
return domainEvents.ToImmutableList();
}
// ref: https://www.meziantou.net/entity-framework-core-generate-tracking-columns.htm
// ref: https://www.meziantou.net/entity-framework-core-soft-delete-using-query-filters.htm
private void OnBeforeSaving()
{
try
{
foreach (var entry in ChangeTracker.Entries())
{
var isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate));
var userId = _currentUserProvider?.GetCurrentUserId() ?? 0;
if (isAuditable)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = userId;
entry.Entity.CreatedAt = DateTime.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = userId;
entry.Entity.LastModified = DateTime.Now;
entry.Entity.Version++;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.Entity.LastModifiedBy = userId;
entry.Entity.LastModified = DateTime.Now;
entry.Entity.IsDeleted = true;
entry.Entity.Version++;
break;
}
}
}
}
catch (System.Exception ex)
{
throw new System.Exception("try for find IAggregate", ex);
}
}
}
================================================
FILE: src/BuildingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs
================================================
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace BuildingBlocks.EFCore
{
public abstract class DesignTimeDbContextFactoryBase : IDesignTimeDbContextFactory where TContext : DbContext
{
public TContext CreateDbContext(string[] args)
{
return Create(Directory.GetCurrentDirectory(), Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
}
protected abstract TContext CreateNewInstance(DbContextOptions options);
public TContext Create()
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var basePath = AppContext.BaseDirectory;
return Create(basePath, environmentName);
}
private TContext Create(string basePath, string environmentName)
{
var builder = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environmentName}.json", true)
.AddEnvironmentVariables();
var config = builder.Build();
var connstr = config.GetConnectionString("DefaultConnection");
if (string.IsNullOrWhiteSpace(connstr))
{
throw new InvalidOperationException(
"Could not find a connection string named 'Default'.");
}
return Create(connstr);
}
private TContext Create(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException(
$"{nameof(connectionString)} is null or empty.",
nameof(connectionString));
var optionsBuilder = new DbContextOptionsBuilder();
Console.WriteLine("DesignTimeDbContextFactory.Create(string): Connection string: {0}", connectionString);
optionsBuilder.UseSqlServer(connectionString);
var options = optionsBuilder.Options;
return CreateNewInstance(options);
}
}
}
================================================
FILE: src/BuildingBlocks/EFCore/EfTxBehavior.cs
================================================
using System.Text.Json;
using System.Transactions;
using BuildingBlocks.Core;
using BuildingBlocks.PersistMessageProcessor;
using BuildingBlocks.Polly;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EFCore;
public class EfTxBehavior(
ILogger> logger,
IDbContext dbContextBase,
IPersistMessageDbContext persistMessageDbContext,
IEventDispatcher eventDispatcher
)
: IPipelineBehavior
where TRequest : notnull, IRequest
where TResponse : notnull
{
public async Task Handle(TRequest request, RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
logger.LogInformation(
"{Prefix} Handled command {MediatrRequest}",
nameof(EfTxBehavior),
typeof(TRequest).FullName);
logger.LogDebug(
"{Prefix} Handled command {MediatrRequest} with content {RequestContent}",
nameof(EfTxBehavior),
typeof(TRequest).FullName,
JsonSerializer.Serialize(request));
logger.LogInformation(
"{Prefix} Open the transaction for {MediatrRequest}",
nameof(EfTxBehavior),
typeof(TRequest).FullName);
//ref: https://learn.microsoft.com/en-us/ef/core/saving/transactions#using-systemtransactions
using var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled);
var response = await next();
logger.LogInformation(
"{Prefix} Executed the {MediatrRequest} request",
nameof(EfTxBehavior),
typeof(TRequest).FullName);
while (true)
{
var domainEvents = dbContextBase.GetDomainEvents();
if (domainEvents is null || !domainEvents.Any())
{
return response;
}
await eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken);
// Save data to database with some retry policy in distributed transaction
await dbContextBase.RetryOnFailure(async () =>
{
await dbContextBase.SaveChangesAsync(cancellationToken);
});
// Save data to database with some retry policy in distributed transaction
await persistMessageDbContext.RetryOnFailure(async () =>
{
await persistMessageDbContext.SaveChangesAsync(cancellationToken);
});
scope.Complete();
return response;
}
}
}
================================================
FILE: src/BuildingBlocks/EFCore/Extensions.cs
================================================
using System.Linq.Expressions;
using BuildingBlocks.Core.Model;
using BuildingBlocks.Web;
using Humanizer;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EFCore;
public static class Extensions
{
public static IServiceCollection AddCustomDbContext(this WebApplicationBuilder builder, string? connectionName = "")
where TContext : DbContext, IDbContext
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
builder.Services.AddValidateOptions();
builder.Services.AddDbContext(
(sp, options) =>
{
var aspireConnectionString = builder.Configuration.GetConnectionString(connectionName.Kebaberize());
var connectionString = aspireConnectionString ?? sp.GetRequiredService().ConnectionString;
ArgumentException.ThrowIfNullOrEmpty(connectionString);
options.UseNpgsql(
connectionString,
dbOptions =>
{
dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name);
})
.UseSnakeCaseNamingConvention();
// Suppress warnings for pending model changes
options.ConfigureWarnings(
w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
});
builder.Services.AddScoped();
builder.Services.AddScoped(sp => sp.GetRequiredService());
return builder.Services;
}
public static IApplicationBuilder UseMigration(this IApplicationBuilder app)
where TContext : DbContext, IDbContext
{
MigrateAsync(app.ApplicationServices).GetAwaiter().GetResult();
SeedAsync(app.ApplicationServices).GetAwaiter().GetResult();
return app;
}
// ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs
public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder)
{
Expression> filterExpr = e => !e.IsDeleted;
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()
.Where(m => m.ClrType.IsAssignableTo(typeof(IEntity))))
{
// modify expression to handle correct child type
var parameter = Expression.Parameter(mutableEntityType.ClrType);
var body = ReplacingExpressionVisitor
.Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body);
var lambdaExpression = Expression.Lambda(body, parameter);
// set filter
mutableEntityType.SetQueryFilter(lambdaExpression);
}
}
// ref: https://andrewlock.net/customising-asp-net-core-identity-ef-core-naming-conventions-for-postgresql/
public static void ToSnakeCaseTables(this ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
// Replace table names
entity.SetTableName(entity.GetTableName()?.Underscore());
var tableObjectIdentifier =
StoreObjectIdentifier.Table(
entity.GetTableName()?.Underscore()!,
entity.GetSchema());
// Replace column names
foreach (var property in entity.GetProperties())
{
property.SetColumnName(property.GetColumnName(tableObjectIdentifier)?.Underscore());
}
foreach (var key in entity.GetKeys())
{
key.SetName(key.GetName()?.Underscore());
}
foreach (var key in entity.GetForeignKeys())
{
key.SetConstraintName(key.GetConstraintName()?.Underscore());
}
}
}
private static async Task MigrateAsync(IServiceProvider serviceProvider)
where TContext : DbContext, IDbContext
{
await using var scope = serviceProvider.CreateAsyncScope();
var context = scope.ServiceProvider.GetRequiredService();
var logger = scope.ServiceProvider.GetRequiredService>();
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
logger.LogInformation("Applying {Count} pending migrations...", pendingMigrations.Count());
await context.Database.MigrateAsync();
logger.LogInformation("Migrations applied successfully.");
}
}
private static async Task SeedAsync(IServiceProvider serviceProvider)
{
await using var scope = serviceProvider.CreateAsyncScope();
var seedersManager = scope.ServiceProvider.GetRequiredService();
await seedersManager.ExecuteSeedAsync();
}
}
================================================
FILE: src/BuildingBlocks/EFCore/IDataSeeder.cs
================================================
namespace BuildingBlocks.EFCore
{
public interface IDataSeeder
{
Task SeedAllAsync();
}
public interface ITestDataSeeder
{
Task SeedAllAsync();
}
}
================================================
FILE: src/BuildingBlocks/EFCore/IDbContext.cs
================================================
using BuildingBlocks.Core.Event;
using Microsoft.EntityFrameworkCore;
namespace BuildingBlocks.EFCore;
using Microsoft.EntityFrameworkCore.Storage;
public interface IDbContext
{
DbSet Set() where TEntity : class;
IReadOnlyList GetDomainEvents();
Task SaveChangesAsync(CancellationToken cancellationToken = default);
Task BeginTransactionAsync(CancellationToken cancellationToken = default);
Task CommitTransactionAsync(CancellationToken cancellationToken = default);
Task RollbackTransactionAsync(CancellationToken cancellationToken = default);
IExecutionStrategy CreateExecutionStrategy();
Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default);
}
================================================
FILE: src/BuildingBlocks/EFCore/ISeedManager.cs
================================================
namespace BuildingBlocks.EFCore;
public interface ISeedManager
{
Task ExecuteSeedAsync();
Task ExecuteTestSeedAsync();
}
================================================
FILE: src/BuildingBlocks/EFCore/PostgresOptions.cs
================================================
namespace BuildingBlocks.EFCore;
public class PostgresOptions
{
public string ConnectionString { get; set; }
}
================================================
FILE: src/BuildingBlocks/EFCore/SeedManagers.cs
================================================
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EFCore;
public class SeedManager(
ILogger logger,
IWebHostEnvironment env,
IServiceProvider serviceProvider
) : ISeedManager
{
public async Task ExecuteSeedAsync()
{
if (!env.IsEnvironment("test"))
{
await using var scope = serviceProvider.CreateAsyncScope();
var dataSeeders = scope.ServiceProvider.GetServices();
foreach (var seeder in dataSeeders)
{
logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name);
await seeder.SeedAllAsync();
logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name);
}
}
}
public async Task ExecuteTestSeedAsync()
{
await using var scope = serviceProvider.CreateAsyncScope();
var testDataSeeders = scope.ServiceProvider.GetServices();
foreach (var testSeeder in testDataSeeders)
{
logger.LogInformation("Seed {SeederName} is started.", testSeeder.GetType().Name);
await testSeeder.SeedAllAsync();
logger.LogInformation("Seed {SeederName} is completed.", testSeeder.GetType().Name);
}
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/BackgroundWorkers/BackgroundWorker.cs
================================================
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EventStoreDB.BackgroundWorkers;
public class BackgroundWorker : BackgroundService
{
private readonly ILogger logger;
private readonly Func perform;
public BackgroundWorker(
ILogger logger,
Func perform
)
{
this.logger = logger;
this.perform = perform;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken) =>
Task.Run(async () =>
{
await Task.Yield();
logger.LogInformation("Background worker stopped");
await perform(stoppingToken);
logger.LogInformation("Background worker stopped");
}, stoppingToken);
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Config.cs
================================================
using System.Reflection;
using BuildingBlocks.EventStoreDB.BackgroundWorkers;
using BuildingBlocks.EventStoreDB.Projections;
using BuildingBlocks.EventStoreDB.Repository;
using BuildingBlocks.EventStoreDB.Subscriptions;
using EventStore.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EventStoreDB;
using Web;
public class EventStoreOptions
{
public string ConnectionString { get; set; } = default!;
}
public record EventStoreDBOptions(
bool UseInternalCheckpointing = true
);
public static class EventStoreDBConfigExtensions
{
public static IServiceCollection AddEventStoreDB(this IServiceCollection services, IConfiguration configuration,
EventStoreDBOptions? options = null)
{
services
.AddSingleton(x =>
{
var aspireConnectionString = configuration.GetConnectionString("eventstore");
var eventStoreOptions = services.GetOptions(nameof(EventStoreOptions));
return new EventStoreClient(EventStoreClientSettings.Create(aspireConnectionString ?? eventStoreOptions.ConnectionString));
})
.AddScoped(typeof(IEventStoreDBRepository<>), typeof(EventStoreDBRepository<>))
.AddTransient();
if (options?.UseInternalCheckpointing != false)
services.AddTransient();
return services;
}
public static IServiceCollection AddEventStoreDBSubscriptionToAll(
this IServiceCollection services,
EventStoreDBSubscriptionToAllOptions? subscriptionOptions = null,
bool checkpointToEventStoreDB = true)
{
if (checkpointToEventStoreDB)
services.AddTransient();
return services.AddHostedService(serviceProvider =>
{
var logger =
serviceProvider.GetRequiredService>();
var eventStoreDBSubscriptionToAll =
serviceProvider.GetRequiredService();
return new BackgroundWorker(
logger,
ct =>
eventStoreDBSubscriptionToAll.SubscribeToAll(
subscriptionOptions ?? new EventStoreDBSubscriptionToAllOptions(),
ct
)
);
}
);
}
public static IServiceCollection AddProjections(this IServiceCollection services,
params Assembly[] assembliesToScan)
{
services.AddSingleton();
RegisterProjections(services, assembliesToScan!);
return services;
}
private static void RegisterProjections(IServiceCollection services, Assembly[] assembliesToScan)
{
services.Scan(scan => scan
.FromAssemblies(assembliesToScan)
.AddClasses(classes => classes.AssignableTo()) // Filter classes
.AsImplementedInterfaces()
.WithTransientLifetime());
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/AggregateEventSourcing.cs
================================================
using BuildingBlocks.Core.Event;
using BuildingBlocks.Core.Model;
namespace BuildingBlocks.EventStoreDB.Events
{
public abstract record AggregateEventSourcing : Entity, IAggregateEventSourcing
{
private readonly List _domainEvents = new();
public IReadOnlyList DomainEvents => _domainEvents.AsReadOnly();
public void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public IDomainEvent[] ClearDomainEvents()
{
var dequeuedEvents = _domainEvents.ToArray();
_domainEvents.Clear();
return dequeuedEvents;
}
public virtual void When(object @event) { }
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/AggregateStreamExtensions.cs
================================================
using BuildingBlocks.EventStoreDB.Serialization;
using EventStore.Client;
namespace BuildingBlocks.EventStoreDB.Events;
public static class AggregateStreamExtensions
{
public static async Task AggregateStream(
this EventStoreClient eventStore,
Guid id,
CancellationToken cancellationToken,
ulong? fromVersion = null
) where T : class, IProjection
{
var readResult = eventStore.ReadStreamAsync(
Direction.Forwards,
StreamNameMapper.ToStreamId(id),
fromVersion ?? StreamPosition.Start,
cancellationToken: cancellationToken
);
// TODO: consider adding extension method for the aggregation and deserialization
var aggregate = (T)Activator.CreateInstance(typeof(T), true)!;
if (await readResult.ReadState == ReadState.StreamNotFound)
{
return null;
}
await foreach (var @event in readResult)
{
var eventData = @event.Deserialize();
aggregate.When(eventData!);
}
return aggregate;
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/EventTypeMapper.cs
================================================
using System.Collections.Concurrent;
using BuildingBlocks.Utils;
namespace BuildingBlocks.EventStoreDB.Events;
public class EventTypeMapper
{
private static readonly EventTypeMapper Instance = new();
private readonly ConcurrentDictionary typeMap = new();
private readonly ConcurrentDictionary typeNameMap = new();
public static void AddCustomMap(string mappedEventTypeName) => AddCustomMap(typeof(T), mappedEventTypeName);
public static void AddCustomMap(Type eventType, string mappedEventTypeName)
{
Instance.typeNameMap.AddOrUpdate(eventType, mappedEventTypeName, (_, _) => mappedEventTypeName);
Instance.typeMap.AddOrUpdate(mappedEventTypeName, eventType, (_, _) => eventType);
}
public static string ToName() => ToName(typeof(TEventType));
public static string ToName(Type eventType) => Instance.typeNameMap.GetOrAdd(eventType, _ =>
{
var eventTypeName = eventType.FullName!.Replace(".", "_", StringComparison.CurrentCulture);
Instance.typeMap.AddOrUpdate(eventTypeName, eventType, (_, _) => eventType);
return eventTypeName;
});
public static Type? ToType(string eventTypeName) => Instance.typeMap.GetOrAdd(eventTypeName, _ =>
{
var type = TypeProvider.GetFirstMatchingTypeFromCurrentDomainAssembly(eventTypeName.Replace("_", ".", StringComparison.CurrentCulture));
if (type == null)
return null;
Instance.typeNameMap.AddOrUpdate(type, eventTypeName, (_, _) => eventTypeName);
return type;
});
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/IAggregateEventSourcing.cs
================================================
using BuildingBlocks.Core.Event;
using BuildingBlocks.Core.Model;
namespace BuildingBlocks.EventStoreDB.Events
{
public interface IAggregateEventSourcing : IProjection, IEntity
{
IReadOnlyList DomainEvents { get; }
IDomainEvent[] ClearDomainEvents();
}
public interface IAggregateEventSourcing : IAggregateEventSourcing, IEntity
{
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/IEventHandler.cs
================================================
using BuildingBlocks.Core.Event;
using MediatR;
namespace BuildingBlocks.EventStoreDB.Events;
public interface IEventHandler : INotificationHandler
where TEvent : IEvent
{
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/IExternalEvent.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.EventStoreDB.Events;
public interface IExternalEvent : IEvent
{
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/IProjection.cs
================================================
namespace BuildingBlocks.EventStoreDB.Events;
public interface IProjection
{
void When(object @event);
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/StreamEvent.cs
================================================
using BuildingBlocks.Core.Event;
namespace BuildingBlocks.EventStoreDB.Events;
public record EventMetadata(
ulong StreamRevision,
ulong LogPosition
);
public class StreamEvent : IEvent
{
public object Data { get; }
public EventMetadata Metadata { get; }
public StreamEvent(object data, EventMetadata metadata)
{
Data = data;
Metadata = metadata;
}
}
public class StreamEvent : StreamEvent where T : notnull
{
public new T Data => (T)base.Data;
public StreamEvent(T data, EventMetadata metadata) : base(data, metadata)
{
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/StreamEventExtensions.cs
================================================
using System.Diagnostics.Eventing.Reader;
using BuildingBlocks.EventStoreDB.Serialization;
using EventStore.Client;
namespace BuildingBlocks.EventStoreDB.Events;
public static class StreamEventExtensions
{
public static StreamEvent? ToStreamEvent(this ResolvedEvent resolvedEvent)
{
var eventData = resolvedEvent.Deserialize();
if (eventData == null)
return null;
var metaData = new EventMetadata(resolvedEvent.Event.EventNumber.ToUInt64(), resolvedEvent.Event.Position.CommitPosition);
var type = typeof(StreamEvent<>).MakeGenericType(eventData.GetType());
return (StreamEvent)Activator.CreateInstance(type, eventData, metaData)!;
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Events/StreamNameMapper.cs
================================================
using System.Collections.Concurrent;
namespace BuildingBlocks.EventStoreDB.Events;
public class StreamNameMapper
{
private static readonly StreamNameMapper Instance = new();
private readonly ConcurrentDictionary TypeNameMap = new();
public static void AddCustomMap(string mappedStreamName) =>
AddCustomMap(typeof(TStream), mappedStreamName);
public static void AddCustomMap(Type streamType, string mappedStreamName)
{
Instance.TypeNameMap.AddOrUpdate(streamType, mappedStreamName, (_, _) => mappedStreamName);
}
public static string ToStreamId(object aggregateId, object? tenantId = null) =>
ToStreamId(typeof(TStream), aggregateId);
public static string ToStreamId(Type streamType, object aggregateId, object? tenantId = null)
{
var tenantPrefix = tenantId != null ? $"{tenantId}_" : "";
return $"{tenantPrefix}{streamType.Name}-{aggregateId}";
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Extensions.cs
================================================
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.EventStoreDB;
using Web;
public static class Extensions
{
// ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce
public static IServiceCollection AddEventStore(
this IServiceCollection services,
IConfiguration configuration,
params Assembly[] assemblies
)
{
services.AddValidateOptions();
var assembliesToScan = assemblies.Length > 0 ? assemblies : new[] { Assembly.GetEntryAssembly()! };
return services
.AddEventStoreDB(configuration)
.AddProjections(assembliesToScan);
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Projections/IProjectionProcessor.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using MediatR;
namespace BuildingBlocks.EventStoreDB.Projections;
public interface IProjectionProcessor
{
Task ProcessEventAsync(StreamEvent streamEvent, CancellationToken cancellationToken = default)
where T : INotification;
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Projections/IProjectionPublisher.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using MediatR;
namespace BuildingBlocks.EventStoreDB.Projections;
public interface IProjectionPublisher
{
Task PublishAsync(StreamEvent streamEvent, CancellationToken cancellationToken = default)
where T : INotification;
Task PublishAsync(StreamEvent streamEvent, CancellationToken cancellationToken = default);
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Projections/ProjectionPublisher.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.EventStoreDB.Projections;
public class ProjectionPublisher : IProjectionPublisher
{
private readonly IServiceProvider _serviceProvider;
public ProjectionPublisher(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task PublishAsync(StreamEvent streamEvent, CancellationToken cancellationToken = default)
where T : INotification
{
using var scope = _serviceProvider.CreateScope();
var projectionsProcessors = scope.ServiceProvider.GetRequiredService>();
foreach (var projectionProcessor in projectionsProcessors)
{
await projectionProcessor.ProcessEventAsync(streamEvent, cancellationToken);
}
}
public Task PublishAsync(StreamEvent streamEvent, CancellationToken cancellationToken = default)
{
var streamData = streamEvent.Data.GetType();
var method = typeof(IProjectionPublisher)
.GetMethods()
.Single(m => m.Name == nameof(PublishAsync) && m.GetGenericArguments().Any())
.MakeGenericMethod(streamData);
return (Task)method
.Invoke(this, new object[] { streamEvent, cancellationToken })!;
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Repository/EventStoreDBRepository.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using BuildingBlocks.EventStoreDB.Serialization;
using EventStore.Client;
namespace BuildingBlocks.EventStoreDB.Repository;
public interface IEventStoreDBRepository where T : class, IAggregateEventSourcing
{
Task Find(Guid id, CancellationToken cancellationToken);
Task Add(T aggregate, CancellationToken cancellationToken);
Task Update(T aggregate, long? expectedRevision = null,
CancellationToken cancellationToken = default);
Task Delete(T aggregate, long? expectedRevision = null, CancellationToken cancellationToken = default);
}
public class EventStoreDBRepository : IEventStoreDBRepository where T : class, IAggregateEventSourcing
{
private static readonly long _currentUserId;
private readonly EventStoreClient eventStore;
public EventStoreDBRepository(EventStoreClient eventStore)
{
this.eventStore = eventStore ?? throw new ArgumentNullException(nameof(eventStore));
}
public Task Find(Guid id, CancellationToken cancellationToken)
{
return eventStore.AggregateStream(
id,
cancellationToken
);
}
public async Task Add(T aggregate, CancellationToken cancellationToken = default)
{
var result = await eventStore.AppendToStreamAsync(
StreamNameMapper.ToStreamId(aggregate.Id),
StreamState.NoStream,
GetEventsToStore(aggregate),
cancellationToken: cancellationToken
);
return result.NextExpectedStreamRevision;
}
public async Task Update(T aggregate, long? expectedRevision = null,
CancellationToken cancellationToken = default)
{
var nextVersion = expectedRevision ?? aggregate.Version;
var result = await eventStore.AppendToStreamAsync(
StreamNameMapper.ToStreamId(aggregate.Id),
(ulong)nextVersion,
GetEventsToStore(aggregate),
cancellationToken: cancellationToken
);
return result.NextExpectedStreamRevision;
}
public Task Delete(T aggregate, long? expectedRevision = null,
CancellationToken cancellationToken = default)
{
return Update(aggregate, expectedRevision, cancellationToken);
}
private static IEnumerable GetEventsToStore(T aggregate)
{
var events = aggregate.ClearDomainEvents();
return events
.Select(EventStoreDBSerializer.ToJsonEventData);
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Repository/RepositoryExtensions.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using BuildingBlocks.Exception;
namespace BuildingBlocks.EventStoreDB.Repository;
public static class RepositoryExtensions
{
public static async Task Get(
this IEventStoreDBRepository repository,
Guid id,
CancellationToken cancellationToken
) where T : class, IAggregateEventSourcing
{
var entity = await repository.Find(id, cancellationToken);
return entity ?? throw AggregateNotFoundException.For(id);
}
public static async Task GetAndUpdate(
this IEventStoreDBRepository repository,
Guid id,
Action action,
long? expectedVersion = null,
CancellationToken cancellationToken = default
) where T : class, IAggregateEventSourcing
{
var entity = await repository.Get(id, cancellationToken);
action(entity);
return await repository.Update(entity, expectedVersion, cancellationToken);
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Serialization/EventStoreDBSerializer.cs
================================================
using System.Text;
using BuildingBlocks.EventStoreDB.Events;
using EventStore.Client;
using Newtonsoft.Json;
namespace BuildingBlocks.EventStoreDB.Serialization;
public static class EventStoreDBSerializer
{
private static readonly JsonSerializerSettings SerializerSettings =
new JsonSerializerSettings().WithNonDefaultConstructorContractResolver();
public static T? Deserialize(this ResolvedEvent resolvedEvent) where T : class =>
Deserialize(resolvedEvent) as T;
public static object? Deserialize(this ResolvedEvent resolvedEvent)
{
// get type
var eventType = EventTypeMapper.ToType(resolvedEvent.Event.EventType);
if (eventType == null)
return null;
// deserialize event
return JsonConvert.DeserializeObject(
Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span),
eventType,
SerializerSettings
)!;
}
public static EventData ToJsonEventData(this object @event) =>
new(
Uuid.NewUuid(),
EventTypeMapper.ToName(@event.GetType()),
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(@event)),
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { }))
);
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Serialization/JsonObjectContractProvider.cs
================================================
using System.Collections.Concurrent;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace BuildingBlocks.EventStoreDB.Serialization;
public static class JsonObjectContractProvider
{
private static readonly Type ConstructorAttributeType = typeof(JsonConstructorAttribute);
private static readonly ConcurrentDictionary Constructors = new();
public static JsonObjectContract UsingNonDefaultConstructor(
JsonObjectContract contract,
Type objectType,
Func> createConstructorParameters) =>
Constructors.GetOrAdd(objectType.AssemblyQualifiedName!, _ =>
{
var nonDefaultConstructor = GetNonDefaultConstructor(objectType);
if (nonDefaultConstructor == null)
return contract;
contract.OverrideCreator = GetObjectConstructor(nonDefaultConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in
createConstructorParameters(nonDefaultConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
return contract;
});
private static ObjectConstructor GetObjectConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c == null)
return a => method.Invoke(null, a)!;
if (!c.GetParameters().Any())
return _ => c.Invoke(Array.Empty());
return a => c.Invoke(a);
}
private static ConstructorInfo? GetNonDefaultConstructor(Type objectType)
{
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum)
return null;
return GetAttributeConstructor(objectType)
?? GetTheMostSpecificConstructor(objectType);
}
private static ConstructorInfo? GetAttributeConstructor(Type objectType)
{
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum)
return null;
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType() == ConstructorAttributeType)).ToList();
return constructors.Count switch
{
1 => constructors[0],
> 1 => throw new JsonException($"Multiple constructors with a {ConstructorAttributeType.Name}."),
_ => null
};
}
private static ConstructorInfo? GetTheMostSpecificConstructor(Type objectType) =>
objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderByDescending(e => e.GetParameters().Length)
.FirstOrDefault();
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Serialization/NonDefaultConstructorContractResolver.cs
================================================
using Newtonsoft.Json.Serialization;
namespace BuildingBlocks.EventStoreDB.Serialization;
public class NonDefaultConstructorContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
return JsonObjectContractProvider.UsingNonDefaultConstructor(
base.CreateObjectContract(objectType),
objectType,
base.CreateConstructorParameters
);
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Serialization/SerializationExtensions.cs
================================================
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BuildingBlocks.EventStoreDB.Serialization;
public static class SerializationExtensions
{
public static JsonSerializerSettings WithDefaults(this JsonSerializerSettings settings)
{
settings.WithNonDefaultConstructorContractResolver()
.Converters.Add(new StringEnumConverter());
return settings;
}
public static JsonSerializerSettings WithNonDefaultConstructorContractResolver(this JsonSerializerSettings settings)
{
settings.ContractResolver = new NonDefaultConstructorContractResolver();
return settings;
}
///
/// Deserialize object from json with JsonNet
///
/// Type of the deserialized object
/// json string
/// deserialized object
public static T FromJson(this string json)
{
return JsonConvert.DeserializeObject(json,
new JsonSerializerSettings().WithNonDefaultConstructorContractResolver())!;
}
///
/// Deserialize object from json with JsonNet
///
/// Type of the deserialized object
/// json string
/// object type
/// deserialized object
public static object FromJson(this string json, Type type)
{
return JsonConvert.DeserializeObject(json, type,
new JsonSerializerSettings().WithNonDefaultConstructorContractResolver())!;
}
///
/// Serialize object to json with JsonNet
///
/// object to serialize
/// json string
public static string ToJson(this object obj)
{
return JsonConvert.SerializeObject(obj);
}
///
/// Serialize object to json with JsonNet
///
/// object to serialize
/// json string
public static StringContent ToJsonStringContent(this object obj)
{
return new StringContent(obj.ToJson(), Encoding.UTF8, "application/json");
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Subscriptions/EventStoreDBSubscriptionCheckpointRepository.cs
================================================
using BuildingBlocks.Core.Event;
using BuildingBlocks.EventStoreDB.Events;
using BuildingBlocks.EventStoreDB.Serialization;
using EventStore.Client;
namespace BuildingBlocks.EventStoreDB.Subscriptions;
public record CheckpointStored(string SubscriptionId, ulong? Position, DateTime CheckpointedAt) : IEvent;
public class EventStoreDBSubscriptionCheckpointRepository : ISubscriptionCheckpointRepository
{
private readonly EventStoreClient eventStoreClient;
public EventStoreDBSubscriptionCheckpointRepository(
EventStoreClient eventStoreClient)
{
this.eventStoreClient = eventStoreClient ?? throw new ArgumentNullException(nameof(eventStoreClient));
}
public async ValueTask Load(string subscriptionId, CancellationToken ct)
{
var streamName = GetCheckpointStreamName(subscriptionId);
var result = eventStoreClient.ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, 1,
cancellationToken: ct);
if (await result.ReadState == ReadState.StreamNotFound)
{
return null;
}
ResolvedEvent? @event = await result.FirstOrDefaultAsync(ct);
return @event?.Deserialize()?.Position;
}
public async ValueTask Store(string subscriptionId, ulong position, CancellationToken ct)
{
var @event = new CheckpointStored(subscriptionId, position, DateTime.UtcNow);
var eventToAppend = new[] { @event.ToJsonEventData() };
var streamName = GetCheckpointStreamName(subscriptionId);
try
{
// store new checkpoint expecting stream to exist
await eventStoreClient.AppendToStreamAsync(
streamName,
StreamState.StreamExists,
eventToAppend,
cancellationToken: ct
);
}
catch (WrongExpectedVersionException)
{
// WrongExpectedVersionException means that stream did not exist
// Set the checkpoint stream to have at most 1 event
// using stream metadata $maxCount property
await eventStoreClient.SetStreamMetadataAsync(
streamName,
StreamState.NoStream,
new StreamMetadata(1),
cancellationToken: ct
);
// append event again expecting stream to not exist
await eventStoreClient.AppendToStreamAsync(
streamName,
StreamState.NoStream,
eventToAppend,
cancellationToken: ct
);
}
}
private static string GetCheckpointStreamName(string subscriptionId) => $"checkpoint_{subscriptionId}";
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Subscriptions/EventStoreDBSubscriptionToAll.cs
================================================
using BuildingBlocks.EventStoreDB.Events;
using BuildingBlocks.EventStoreDB.Projections;
using BuildingBlocks.Utils;
using EventStore.Client;
using Grpc.Core;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.EventStoreDB.Subscriptions;
public class EventStoreDBSubscriptionToAllOptions
{
public string SubscriptionId { get; set; } = "default";
public SubscriptionFilterOptions FilterOptions { get; set; } =
new(EventTypeFilter.ExcludeSystemEvents());
public Action? ConfigureOperation { get; set; }
public UserCredentials? Credentials { get; set; }
public bool ResolveLinkTos { get; set; }
public bool IgnoreDeserializationErrors { get; set; } = true;
}
public class EventStoreDBSubscriptionToAll
{
private readonly IProjectionPublisher projectionPublisher;
private readonly EventStoreClient eventStoreClient;
private readonly IMediator _mediator;
private readonly ISubscriptionCheckpointRepository checkpointRepository;
private readonly ILogger logger;
private EventStoreDBSubscriptionToAllOptions subscriptionOptions = default!;
private string SubscriptionId => subscriptionOptions.SubscriptionId;
private readonly object resubscribeLock = new();
private CancellationToken cancellationToken;
public EventStoreDBSubscriptionToAll(
EventStoreClient eventStoreClient,
IMediator mediator,
IProjectionPublisher projectionPublisher,
ISubscriptionCheckpointRepository checkpointRepository,
ILogger logger
)
{
this.projectionPublisher = projectionPublisher;
this.eventStoreClient = eventStoreClient ?? throw new ArgumentNullException(nameof(eventStoreClient));
_mediator = mediator;
this.checkpointRepository =
checkpointRepository ?? throw new ArgumentNullException(nameof(checkpointRepository));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task SubscribeToAll(EventStoreDBSubscriptionToAllOptions subscriptionOptions, CancellationToken ct)
{
// see: https://github.com/dotnet/runtime/issues/36063
await Task.Yield();
this.subscriptionOptions = subscriptionOptions;
cancellationToken = ct;
logger.LogInformation("Subscription to all '{SubscriptionId}'", subscriptionOptions.SubscriptionId);
var checkpoint = await checkpointRepository.Load(SubscriptionId, ct);
await eventStoreClient.SubscribeToAllAsync(
checkpoint == null ? FromAll.Start : FromAll.After(new Position(checkpoint.Value, checkpoint.Value)),
HandleEvent,
subscriptionOptions.ResolveLinkTos,
HandleDrop,
subscriptionOptions.FilterOptions,
subscriptionOptions.Credentials,
ct
);
logger.LogInformation("Subscription to all '{SubscriptionId}' started", SubscriptionId);
}
private async Task HandleEvent(StreamSubscription subscription, ResolvedEvent resolvedEvent,
CancellationToken ct)
{
try
{
if (IsEventWithEmptyData(resolvedEvent) || IsCheckpointEvent(resolvedEvent))
return;
var streamEvent = resolvedEvent.ToStreamEvent();
if (streamEvent == null)
{
// That can happen if we're sharing database between modules.
// If we're subscribing to all and not filtering out events from other modules,
// then we might get events that are from other module and we might not be able to deserialize them.
// In that case it's safe to ignore deserialization error.
// You may add more sophisticated logic checking if it should be ignored or not.
logger.LogWarning("Couldn't deserialize event with id: {EventId}", resolvedEvent.Event.EventId);
if (!subscriptionOptions.IgnoreDeserializationErrors)
throw new InvalidOperationException($"Unable to deserialize event {resolvedEvent.Event.EventType} with id: {resolvedEvent.Event.EventId}");
return;
}
// publish event to internal event bus
await _mediator.Publish(streamEvent, ct);
await projectionPublisher.PublishAsync(streamEvent, ct);
await checkpointRepository.Store(SubscriptionId, resolvedEvent.Event.Position.CommitPosition, ct);
}
catch (System.Exception e)
{
logger.LogError("Error consuming message: {ExceptionMessage}{ExceptionStackTrace}", e.Message,
e.StackTrace);
// if you're fine with dropping some events instead of stopping subscription
// then you can add some logic if error should be ignored
throw;
}
}
private void HandleDrop(StreamSubscription _, SubscriptionDroppedReason reason, System.Exception? exception)
{
logger.LogError(
exception,
"Subscription to all '{SubscriptionId}' dropped with '{Reason}'",
SubscriptionId,
reason
);
if (exception is RpcException { StatusCode: StatusCode.Cancelled })
return;
Resubscribe();
}
private void Resubscribe()
{
// You may consider adding a max resubscribe count if you want to fail process
// instead of retrying until database is up
while (true)
{
var resubscribed = false;
try
{
Monitor.Enter(resubscribeLock);
// No synchronization context is needed to disable synchronization context.
// That enables running asynchronous method not causing deadlocks.
// As this is a background process then we don't need to have async context here.
using (NoSynchronizationContextScope.Enter())
{
SubscribeToAll(subscriptionOptions, cancellationToken).Wait(cancellationToken);
}
resubscribed = true;
}
catch (System.Exception exception)
{
logger.LogWarning(exception,
"Failed to resubscribe to all '{SubscriptionId}' dropped with '{ExceptionMessage}{ExceptionStackTrace}'",
SubscriptionId, exception.Message, exception.StackTrace);
}
finally
{
Monitor.Exit(resubscribeLock);
}
if (resubscribed)
break;
// Sleep between reconnections to not flood the database or not kill the CPU with infinite loop
// Randomness added to reduce the chance of multiple subscriptions trying to reconnect at the same time
Thread.Sleep(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000));
}
}
private bool IsEventWithEmptyData(ResolvedEvent resolvedEvent)
{
if (resolvedEvent.Event.Data.Length != 0)
return false;
logger.LogInformation("Event without data received");
return true;
}
private bool IsCheckpointEvent(ResolvedEvent resolvedEvent)
{
if (resolvedEvent.Event.EventType != EventTypeMapper.ToName())
return false;
logger.LogInformation("Checkpoint event - ignoring");
return true;
}
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Subscriptions/ISubscriptionCheckpointRepository.cs
================================================
namespace BuildingBlocks.EventStoreDB.Subscriptions;
public interface ISubscriptionCheckpointRepository
{
ValueTask Load(string subscriptionId, CancellationToken ct);
ValueTask Store(string subscriptionId, ulong position, CancellationToken ct);
}
================================================
FILE: src/BuildingBlocks/EventStoreDB/Subscriptions/InMemorySubscriptionCheckpointRepository.cs
================================================
using System.Collections.Concurrent;
namespace BuildingBlocks.EventStoreDB.Subscriptions;
public class InMemorySubscriptionCheckpointRepository : ISubscriptionCheckpointRepository
{
private readonly ConcurrentDictionary checkpoints = new();
public ValueTask Load(string subscriptionId, CancellationToken ct)
{
return new(checkpoints.TryGetValue(subscriptionId, out var checkpoint) ? checkpoint : null);
}
public ValueTask Store(string subscriptionId, ulong position, CancellationToken ct)
{
checkpoints.AddOrUpdate(subscriptionId, position, (_, _) => position);
return ValueTask.CompletedTask;
}
}
================================================
FILE: src/BuildingBlocks/Exception/AggregateNotFoundException.cs
================================================
namespace BuildingBlocks.Exception;
public class AggregateNotFoundException : System.Exception
{
public AggregateNotFoundException(string typeName, Guid id) : base($"{typeName} with id '{id}' was not found")
{
}
public static AggregateNotFoundException For(Guid id)
{
return new AggregateNotFoundException(typeof(T).Name, id);
}
}
================================================
FILE: src/BuildingBlocks/Exception/AppException.cs
================================================
using System.Net;
namespace BuildingBlocks.Exception;
public class AppException : CustomException
{
public AppException(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest, int? code = null) : base(message, statusCode, code: code)
{
}
public AppException(string message, System.Exception innerException, HttpStatusCode statusCode = HttpStatusCode.BadRequest, int? code = null) : base(message, innerException, statusCode, code)
{
}
}
================================================
FILE: src/BuildingBlocks/Exception/BadRequestException.cs
================================================
using System;
using System.Net;
namespace BuildingBlocks.Exception
{
public class BadRequestException : CustomException
{
public BadRequestException(string message, int? code = null) : base(message, HttpStatusCode.BadRequest, code: code)
{
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/ConflictException.cs
================================================
using System.Net;
namespace BuildingBlocks.Exception
{
public class ConflictException : CustomException
{
public ConflictException(string message, int? code = null) : base(message, HttpStatusCode.Conflict, code: code)
{
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/CustomException.cs
================================================
using System.Net;
namespace BuildingBlocks.Exception;
public class CustomException : System.Exception
{
public CustomException(
string message,
HttpStatusCode statusCode = HttpStatusCode.InternalServerError,
int? code = null) : base(message)
{
StatusCode = statusCode;
Code = code;
}
public CustomException(
string message,
System.Exception innerException,
HttpStatusCode statusCode = HttpStatusCode.InternalServerError,
int? code = null) : base(message, innerException)
{
StatusCode = statusCode;
Code = code;
}
public CustomException(
HttpStatusCode statusCode = HttpStatusCode.InternalServerError,
int? code = null) : base()
{
StatusCode = statusCode;
Code = code;
}
public HttpStatusCode StatusCode { get; }
public int? Code { get; }
}
================================================
FILE: src/BuildingBlocks/Exception/DomainException.cs
================================================
using System.Net;
using BuildingBlocks.Exception;
namespace SmartCharging.Infrastructure.Exceptions
{
public class DomainException : CustomException
{
public DomainException(string message, HttpStatusCode statusCode = HttpStatusCode.BadRequest) : base(message, statusCode)
{
}
public DomainException(string message, Exception innerException, HttpStatusCode statusCode = HttpStatusCode.BadRequest, int? code = null) : base(message, innerException, statusCode, code)
{
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/GrpcExceptionInterceptor.cs
================================================
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.Exception;
public class GrpcExceptionInterceptor : Interceptor
{
public override async Task UnaryServerHandler(
TRequest request,
ServerCallContext context,
UnaryServerMethod continuation)
{
try
{
return await continuation(request, context);
}
catch (System.Exception exception)
{
throw new RpcException(new Status(StatusCode.Internal, exception.Message));
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/InternalServerException.cs
================================================
using System.Globalization;
using System.Net;
namespace BuildingBlocks.Exception
{
public class InternalServerException : CustomException
{
public InternalServerException() : base() { }
public InternalServerException(string message, int? code) : base(message, code: code) { }
public InternalServerException(string message, int? code = null, params object[] args)
: base(message: String.Format(CultureInfo.CurrentCulture, message, args, HttpStatusCode.InternalServerError, code))
{
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/NotFoundException.cs
================================================
using System.Net;
namespace BuildingBlocks.Exception
{
public class NotFoundException : CustomException
{
public NotFoundException(string message, int? code = null) : base(message, HttpStatusCode.NotFound, code: code)
{
}
}
}
================================================
FILE: src/BuildingBlocks/Exception/ProblemDetailsWithCode.cs
================================================
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
namespace BuildingBlocks.Exception;
using ProblemDetails = Microsoft.AspNetCore.Mvc.ProblemDetails;
public class ProblemDetailsWithCode : ProblemDetails
{
[JsonPropertyName("code")]
public int? Code { get; set; }
}
================================================
FILE: src/BuildingBlocks/Exception/ValidationException.cs
================================================
using System.Net;
namespace BuildingBlocks.Exception
{
public class ValidationException : CustomException
{
public ValidationException(string message, int? code = null) : base(message, HttpStatusCode.BadRequest, code: code)
{
}
}
}
================================================
FILE: src/BuildingBlocks/HealthCheck/Extensions.cs
================================================
using BuildingBlocks.EFCore;
using BuildingBlocks.EventStoreDB;
using BuildingBlocks.MassTransit;
using BuildingBlocks.Mongo;
using BuildingBlocks.Web;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using MongoDB.Driver;
using RabbitMQ.Client;
namespace BuildingBlocks.HealthCheck;
public static class Extensions
{
private const string HealthEndpointPath = "/health";
private const string AlivenessEndpointPath = "/alive";
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services)
{
var healthOptions = services.GetOptions(nameof(HealthOptions));
if (healthOptions.Enabled)
{
var appOptions = services.GetOptions(nameof(AppOptions));
var postgresOptions = services.GetOptions(nameof(PostgresOptions));
var rabbitMqOptions = services.GetOptions(nameof(RabbitMqOptions));
var eventStoreOptions = services.GetOptions(nameof(EventStoreOptions));
var mongoOptions = services.GetOptions(nameof(MongoOptions));
var healthChecksBuilder = services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
.AddRabbitMQ(
serviceProvider =>
{
var factory = new ConnectionFactory
{
Uri = new Uri($"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}"),
};
return factory.CreateConnectionAsync();
});
if (!string.IsNullOrEmpty(mongoOptions.ConnectionString))
{
healthChecksBuilder.AddMongoDb(
clientFactory: _ => new MongoClient(mongoOptions.ConnectionString),
name: "MongoDB-Health",
failureStatus: HealthStatus.Unhealthy,
timeout: TimeSpan.FromSeconds(10));
}
if (!string.IsNullOrEmpty(postgresOptions.ConnectionString))
healthChecksBuilder.AddNpgSql(postgresOptions.ConnectionString);
if (!string.IsNullOrEmpty(eventStoreOptions.ConnectionString))
healthChecksBuilder.AddEventStore(eventStoreOptions.ConnectionString);
services.AddHealthChecksUI(setup =>
{
setup.SetEvaluationTimeInSeconds(60); // time in seconds between check
setup.AddHealthCheckEndpoint($"Self Check - {appOptions.Name}", HealthEndpointPath);
}).AddInMemoryStorage();
}
services.AddHealthChecks().AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return services;
}
public static WebApplication UseCustomHealthCheck(this WebApplication app)
{
var healthOptions = app.Configuration.GetOptions(nameof(HealthOptions));
if (app.Environment.IsDevelopment())
{
app.MapHealthChecks(HealthEndpointPath);
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),
});
}
if (healthOptions.Enabled)
app.MapHealthChecksUI(options => options.UIPath = "/health-ui");
return app;
}
}
================================================
FILE: src/BuildingBlocks/HealthCheck/HealthOptions.cs
================================================
namespace BuildingBlocks.HealthCheck;
public class HealthOptions
{
public bool Enabled { get; set; } = true;
}
================================================
FILE: src/BuildingBlocks/Jwt/AuthHeaderHandler.cs
================================================
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
namespace BuildingBlocks.Jwt;
public class AuthHeaderHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContext;
public AuthHeaderHandler(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
protected override Task SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var token = (_httpContext?.HttpContext?.Request.Headers["Authorization"])?.ToString();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token?.Replace("Bearer ", "", StringComparison.CurrentCulture));
return base.SendAsync(request, cancellationToken);
}
}
================================================
FILE: src/BuildingBlocks/Jwt/JwtExtensions.cs
================================================
using BuildingBlocks.Constants;
using BuildingBlocks.Web;
using Duende.IdentityServer.EntityFramework.Entities;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace BuildingBlocks.Jwt
{
public static class JwtExtensions
{
public static IServiceCollection AddJwt(this IServiceCollection services)
{
// Bind Jwt settings from configuration
var jwtOptions = services.GetOptions("Jwt");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = jwtOptions.Authority;
options.Audience = jwtOptions.Audience;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuers = [jwtOptions.Authority],
ValidateAudience = true,
ValidAudiences = [jwtOptions.Audience],
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(2), // Reduce default clock skew
// For IdentityServer4/Duende, we should also validate the signing key
ValidateIssuerSigningKey = true,
NameClaimType = "name", // Map "name" claim to User.Identity.Name
RoleClaimType = "role", // Map "role" claim to User.IsInRole()
};
// Preserve ALL claims from the token (including "sub")
options.MapInboundClaims = false;
});
services.AddAuthorization(
options =>
{
options.AddPolicy(
nameof(ApiScope),
policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", jwtOptions.Audience);
});
// Role-based policies
options.AddPolicy(
IdentityConstant.Role.Admin,
x =>
{
x.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
x.RequireRole(IdentityConstant.Role.Admin);
}
);
options.AddPolicy(
IdentityConstant.Role.User,
x =>
{
x.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
x.RequireRole(IdentityConstant.Role.User);
}
);
});
return services;
}
}
}
================================================
FILE: src/BuildingBlocks/Logging/LoggingBehavior.cs
================================================
using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BuildingBlocks.Logging;
public class LoggingBehavior : IPipelineBehavior
where TRequest : notnull, IRequest
where TResponse : notnull
{
private readonly ILogger> _logger;
public LoggingBehavior(ILogger> logger)
{
_logger = logger;
}
public async Task Handle(TRequest request, RequestHandlerDelegate next,
CancellationToken cancellationToken)
{
const string prefix = nameof(LoggingBehavior);
_logger.LogInformation("[{Prefix}] Handle request={X-RequestData} and response={X-ResponseData}",
prefix, typeof(TRequest).Name, typeof(TResponse).Name);
var timer = new Stopwatch();
timer.Start();
var response = await next();
timer.Stop();
var timeTaken = timer.Elapsed;
if (timeTaken.Seconds > 3) // if the request is greater than 3 seconds, then log the warnings
_logger.LogWarning("[{Perf-Possible}] The request {X-RequestData} took {TimeTaken} seconds.",
prefix, typeof(TRequest).Name, timeTaken.Seconds);
_logger.LogInformation("[{Prefix}] Handled {X-RequestData}", prefix, typeof(TRequest).Name);
return response;
}
}
================================================
FILE: src/BuildingBlocks/Mapster/Extensions.cs
================================================
using System.Reflection;
using Mapster;
using MapsterMapper;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Mapster;
public static class Extensions
{
public static IServiceCollection AddCustomMapster(this IServiceCollection services, params Assembly[] assemblies)
{
var typeAdapterConfig = TypeAdapterConfig.GlobalSettings;
typeAdapterConfig.Scan(assemblies);
var mapperConfig = new Mapper(typeAdapterConfig);
services.AddSingleton(mapperConfig);
return services;
}
}
================================================
FILE: src/BuildingBlocks/MassTransit/ConsumeFilter.cs
================================================
using BuildingBlocks.Core.Event;
using BuildingBlocks.PersistMessageProcessor;
using MassTransit;
namespace BuildingBlocks.MassTransit;
// Handle inbox messages with masstransit pipeline
public class ConsumeFilter : IFilter>
where T : class
{
private readonly IPersistMessageProcessor _persistMessageProcessor;
public ConsumeFilter(IPersistMessageProcessor persistMessageProcessor)
{
_persistMessageProcessor = persistMessageProcessor;
}
public async Task Send(ConsumeContext context, IPipe> next)
{
var id = await _persistMessageProcessor.AddReceivedMessageAsync(
new MessageEnvelope(
context.Message,
context.Headers.ToDictionary(x => x.Key, x => x.Value))
);
var message = await _persistMessageProcessor.ExistMessageAsync(id);
if (message is null)
{
await next.Send(context);
await _persistMessageProcessor.ProcessInboxAsync(id);
}
}
public void Probe(ProbeContext context)
{
}
}
================================================
FILE: src/BuildingBlocks/MassTransit/Extensions.cs
================================================
using System.Reflection;
using BuildingBlocks.Web;
using MassTransit;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BuildingBlocks.MassTransit;
using Exception;
public static class Extensions
{
public static IServiceCollection AddCustomMassTransit(
this IServiceCollection services,
IWebHostEnvironment env,
TransportType transportType,
params Assembly[] assembly
)
{
services.AddValidateOptions();
if (env.IsEnvironment("test"))
{
services.AddMassTransitTestHarness(
configure =>
{
SetupMasstransitConfigurations(services, configure, transportType, assembly);
});
}
else
{
services.AddMassTransit(
configure =>
{
SetupMasstransitConfigurations(services, configure, transportType, assembly);
});
}
return services;
}
private static void SetupMasstransitConfigurations(
IServiceCollection services,
IBusRegistrationConfigurator configure,
TransportType transportType,
params Assembly[] assembly
)
{
configure.AddConsumers(assembly);
configure.AddSagaStateMachines(assembly);
configure.AddSagas(assembly);
configure.AddActivities(assembly);
switch (transportType)
{
case TransportType.RabbitMq:
configure.UsingRabbitMq(
(context, configurator) =>
{
var configuration = context.GetRequiredService();
var aspireConnectionString = configuration.GetConnectionString("rabbitmq");
if (!string.IsNullOrEmpty(aspireConnectionString))
{
configurator.Host(new Uri(aspireConnectionString));
}
else
{
var rabbitMqOptions = services.GetOptions(nameof(RabbitMqOptions));
ArgumentNullException.ThrowIfNull(rabbitMqOptions);
configurator.Host(
rabbitMqOptions?.HostName,
rabbitMqOptions?.Port ?? 5672,
"/",
h =>
{
h.Username(rabbitMqOptions.UserName);
h.Password(rabbitMqOptions.Password);
});
}
configurator.ConfigureEndpoints(context);
configurator.UseMessageRetry(AddRetryConfiguration);
});
break;
case TransportType.InMemory:
configure.UsingInMemory(
(context, configurator) =>
{
configurator.ConfigureEndpoints(context);
configurator.UseMessageRetry(AddRetryConfiguration);
});
break;
default:
throw new ArgumentOutOfRangeException(
nameof(transportType),
transportType,
message: null);
}
}
private static void AddRetryConfiguration(IRetryConfigurator retryConfigurator)
{
retryConfigurator.Exponential(
3,
TimeSpan.FromMilliseconds(200),
TimeSpan.FromMinutes(120),
TimeSpan.FromMilliseconds(200))
.Ignore<
ValidationException>(); // don't retry if we have invalid data and message goes to _error queue masstransit
}
}
================================================
FILE: src/BuildingBlocks/MassTransit/RabbitMqOptions.cs
================================================
namespace BuildingBlocks.MassTransit;
public class RabbitMqOptions
{
public string HostName { get; set; }
public string ExchangeName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public ushort? Port { get; set; }
}
================================================
FILE: src/BuildingBlocks/MassTransit/TransportType.cs
================================================
namespace BuildingBlocks.MassTransit;
public enum TransportType
{
RabbitMq,
InMemory
}
================================================
FILE: src/BuildingBlocks/Mongo/Extensions.cs
================================================
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BuildingBlocks.Mongo
{
using Web;
public static class Extensions
{
public static IServiceCollection AddMongoDbContext(
this WebApplicationBuilder builder, Action? configurator = null)
where TContext : MongoDbContext
{
return builder.Services.AddMongoDbContext(builder.Configuration, configurator);
}
public static IServiceCollection AddMongoDbContext