├── 02-publish_messages ├── 1-add_rabbitmq │ └── docker-compose.yml ├── 2-publish_event_to_rabbitmq │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ │ └── http │ │ │ ├── seller_backoffice │ │ │ ├── product-GET.http │ │ │ ├── product-PUT.http │ │ │ └── products-GET.http │ │ │ └── shop │ │ │ ├── product-GET.http │ │ │ ├── product_review-PUT.http │ │ │ ├── product_reviews-GET.http │ │ │ ├── products-GET.http │ │ │ ├── user-GET.http │ │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── codely.svg │ ├── src │ │ ├── app │ │ │ └── api │ │ │ │ ├── seller_backoffice │ │ │ │ └── products │ │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── shop │ │ │ │ ├── product_reviews │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ ├── products │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ └── users │ │ │ │ └── [id] │ │ │ │ └── route.ts │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── send_welcome_email │ │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ │ └── domain │ │ │ │ │ ├── Email.ts │ │ │ │ │ ├── EmailBody.ts │ │ │ │ │ ├── EmailId.ts │ │ │ │ │ ├── EmailSender.ts │ │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── .gitkeep │ │ │ │ └── update_last_activity_date │ │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ │ └── domain │ │ │ │ ├── RetentionUser.ts │ │ │ │ └── RetentionUserRepository.ts │ │ │ ├── seller_backoffice │ │ │ └── products │ │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductCreator.ts │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRepository.ts │ │ │ │ └── ProductViews.ts │ │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── AggregateRoot.ts │ │ │ │ ├── Collection.ts │ │ │ │ ├── EmailAddress.ts │ │ │ │ ├── Identifier.ts │ │ │ │ ├── Money.ts │ │ │ │ ├── StringValueObject.ts │ │ │ │ ├── UuidGenerator.ts │ │ │ │ └── event │ │ │ │ │ ├── DomainEvent.ts │ │ │ │ │ ├── DomainEventName.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ └── event_bus │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── rabbitmq │ │ │ │ ├── RabbitMqConnection.ts │ │ │ │ └── RabbitMqEventBus.ts │ │ │ └── shop │ │ │ ├── product_reviews │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductReviewCreator.ts │ │ │ │ └── search_by_product_id │ │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ │ ├── domain │ │ │ │ ├── ProductReview.ts │ │ │ │ ├── ProductReviewComment.ts │ │ │ │ ├── ProductReviewId.ts │ │ │ │ ├── ProductReviewRating.ts │ │ │ │ └── ProductReviewRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductReviewRepository.ts │ │ │ ├── products │ │ │ ├── application │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductFeaturedReview.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRating.ts │ │ │ │ └── ProductRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.ts │ │ │ ├── find │ │ │ │ └── UserFinder.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.ts │ │ │ ├── search │ │ │ │ └── UserSearcher.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.ts │ │ │ ├── domain │ │ │ ├── User.ts │ │ │ ├── UserArchivedDomainEvent.ts │ │ │ ├── UserDoesNotExist.ts │ │ │ ├── UserEmail.ts │ │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ │ ├── UserFinder.ts │ │ │ ├── UserId.ts │ │ │ ├── UserName.ts │ │ │ ├── UserProfilePicture.ts │ │ │ ├── UserRegisteredDomainEvent.ts │ │ │ ├── UserRepository.ts │ │ │ └── UserStatus.ts │ │ │ └── infrastructure │ │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ │ ├── domain │ │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ │ ├── EmailIdMother.ts │ │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── MockEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ │ ├── domain │ │ │ │ └── RetentionUserMother.ts │ │ │ │ └── infrastructure │ │ │ │ └── MockRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── EmailAddressMother.ts │ │ │ │ └── EnumMother.ts │ │ │ └── infrastructure │ │ │ │ ├── MockEventBus.ts │ │ │ │ └── MockUuidGenerator.ts │ │ │ └── shop │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.test.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.test.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.test.ts │ │ │ ├── domain │ │ │ ├── DateMother.ts │ │ │ ├── UserArchivedDomainEventMother.ts │ │ │ ├── UserEmailMother.ts │ │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ │ ├── UserIdMother.ts │ │ │ ├── UserMother.ts │ │ │ ├── UserNameMother.ts │ │ │ ├── UserProfilePictureMother.ts │ │ │ └── UserRegisteredDomainEventMother.ts │ │ │ └── infrastructure │ │ │ └── MockUserRepository.ts │ └── tsconfig.json └── 3-publication_fallback │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ └── http │ │ ├── seller_backoffice │ │ ├── product-GET.http │ │ ├── product-PUT.http │ │ └── products-GET.http │ │ ├── shared │ │ └── event_bus-POST.http │ │ └── shop │ │ ├── product-GET.http │ │ ├── product_review-PUT.http │ │ ├── product_reviews-GET.http │ │ ├── products-GET.http │ │ ├── user-GET.http │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── codely.svg │ ├── src │ ├── app │ │ └── api │ │ │ ├── seller_backoffice │ │ │ └── products │ │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── shared │ │ │ └── event_bus │ │ │ │ └── route.ts │ │ │ └── shop │ │ │ ├── product_reviews │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ │ ├── products │ │ │ ├── [id] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ │ └── users │ │ │ └── [id] │ │ │ └── route.ts │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── send_welcome_email │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ └── domain │ │ │ │ ├── Email.ts │ │ │ │ ├── EmailBody.ts │ │ │ │ ├── EmailId.ts │ │ │ │ ├── EmailSender.ts │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ └── user │ │ │ ├── application │ │ │ ├── create │ │ │ │ └── .gitkeep │ │ │ └── update_last_activity_date │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ └── domain │ │ │ ├── RetentionUser.ts │ │ │ └── RetentionUserRepository.ts │ │ ├── seller_backoffice │ │ └── products │ │ │ ├── application │ │ │ ├── create │ │ │ │ └── ProductCreator.ts │ │ │ ├── search │ │ │ │ └── ProductSearcher.ts │ │ │ └── search_all │ │ │ │ └── AllProductsSearcher.ts │ │ │ ├── domain │ │ │ ├── Product.ts │ │ │ ├── ProductId.ts │ │ │ ├── ProductImageUrl.ts │ │ │ ├── ProductImageUrls.ts │ │ │ ├── ProductName.ts │ │ │ ├── ProductRepository.ts │ │ │ └── ProductViews.ts │ │ │ └── infrastructure │ │ │ └── MySqlProductRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── AggregateRoot.ts │ │ │ ├── Collection.ts │ │ │ ├── EmailAddress.ts │ │ │ ├── Identifier.ts │ │ │ ├── Money.ts │ │ │ ├── StringValueObject.ts │ │ │ ├── UuidGenerator.ts │ │ │ └── event │ │ │ │ ├── DomainEvent.ts │ │ │ │ ├── DomainEventName.ts │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ └── EventBus.ts │ │ └── infrastructure │ │ │ ├── MariaDBConnection.ts │ │ │ └── event_bus │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ ├── failover │ │ │ └── DomainEventFailover.ts │ │ │ └── rabbitmq │ │ │ ├── RabbitMqConnection.ts │ │ │ └── RabbitMqEventBus.ts │ │ └── shop │ │ ├── product_reviews │ │ ├── application │ │ │ ├── create │ │ │ │ └── ProductReviewCreator.ts │ │ │ └── search_by_product_id │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ ├── domain │ │ │ ├── ProductReview.ts │ │ │ ├── ProductReviewComment.ts │ │ │ ├── ProductReviewId.ts │ │ │ ├── ProductReviewRating.ts │ │ │ └── ProductReviewRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductReviewRepository.ts │ │ ├── products │ │ ├── application │ │ │ ├── search │ │ │ │ └── ProductSearcher.ts │ │ │ └── search_all │ │ │ │ └── AllProductsSearcher.ts │ │ ├── domain │ │ │ ├── Product.ts │ │ │ ├── ProductFeaturedReview.ts │ │ │ ├── ProductId.ts │ │ │ ├── ProductImageUrl.ts │ │ │ ├── ProductImageUrls.ts │ │ │ ├── ProductName.ts │ │ │ ├── ProductRating.ts │ │ │ └── ProductRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductRepository.ts │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.ts │ │ ├── find │ │ │ └── UserFinder.ts │ │ ├── registrar │ │ │ └── UserRegistrar.ts │ │ ├── search │ │ │ └── UserSearcher.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.ts │ │ ├── domain │ │ ├── User.ts │ │ ├── UserArchivedDomainEvent.ts │ │ ├── UserDoesNotExist.ts │ │ ├── UserEmail.ts │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ ├── UserFinder.ts │ │ ├── UserId.ts │ │ ├── UserName.ts │ │ ├── UserProfilePicture.ts │ │ ├── UserRegisteredDomainEvent.ts │ │ ├── UserRepository.ts │ │ └── UserStatus.ts │ │ └── infrastructure │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ ├── domain │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ ├── EmailIdMother.ts │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ └── infrastructure │ │ │ │ └── MockEmailSender.ts │ │ └── user │ │ │ ├── application │ │ │ └── update_last_activity_date │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ ├── domain │ │ │ └── RetentionUserMother.ts │ │ │ └── infrastructure │ │ │ └── MockRetentionUserRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── EmailAddressMother.ts │ │ │ └── EnumMother.ts │ │ └── infrastructure │ │ │ ├── MockEventBus.ts │ │ │ └── MockUuidGenerator.ts │ │ └── shop │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.test.ts │ │ ├── registrar │ │ │ └── UserRegistrar.test.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.test.ts │ │ ├── domain │ │ ├── DateMother.ts │ │ ├── UserArchivedDomainEventMother.ts │ │ ├── UserEmailMother.ts │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ ├── UserIdMother.ts │ │ ├── UserMother.ts │ │ ├── UserNameMother.ts │ │ ├── UserProfilePictureMother.ts │ │ └── UserRegisteredDomainEventMother.ts │ │ └── infrastructure │ │ └── MockUserRepository.ts │ └── tsconfig.json ├── 04-generate_queues_automatically ├── 1-create_queues_in_rabbitmq │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ │ └── http │ │ │ ├── seller_backoffice │ │ │ ├── product-GET.http │ │ │ ├── product-PUT.http │ │ │ └── products-GET.http │ │ │ ├── shared │ │ │ └── event_bus-POST.http │ │ │ └── shop │ │ │ ├── product-GET.http │ │ │ ├── product_review-PUT.http │ │ │ ├── product_reviews-GET.http │ │ │ ├── products-GET.http │ │ │ ├── user-GET.http │ │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── codely.svg │ ├── src │ │ ├── app │ │ │ ├── api │ │ │ │ ├── seller_backoffice │ │ │ │ │ └── products │ │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── route.ts │ │ │ │ ├── shared │ │ │ │ │ └── event_bus │ │ │ │ │ │ └── route.ts │ │ │ │ └── shop │ │ │ │ │ ├── product_reviews │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ ├── products │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ └── users │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── scripts │ │ │ │ └── configure-rabbitmq.ts │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── send_welcome_email │ │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ │ └── domain │ │ │ │ │ ├── Email.ts │ │ │ │ │ ├── EmailBody.ts │ │ │ │ │ ├── EmailId.ts │ │ │ │ │ ├── EmailSender.ts │ │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── .gitkeep │ │ │ │ └── update_last_activity_date │ │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ │ └── domain │ │ │ │ ├── RetentionUser.ts │ │ │ │ └── RetentionUserRepository.ts │ │ │ ├── seller_backoffice │ │ │ └── products │ │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductCreator.ts │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRepository.ts │ │ │ │ └── ProductViews.ts │ │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── AggregateRoot.ts │ │ │ │ ├── Collection.ts │ │ │ │ ├── EmailAddress.ts │ │ │ │ ├── Identifier.ts │ │ │ │ ├── Money.ts │ │ │ │ ├── StringValueObject.ts │ │ │ │ ├── UuidGenerator.ts │ │ │ │ └── event │ │ │ │ │ ├── DomainEvent.ts │ │ │ │ │ ├── DomainEventName.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ └── event_bus │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ ├── failover │ │ │ │ └── DomainEventFailover.ts │ │ │ │ └── rabbitmq │ │ │ │ ├── RabbitMqConnection.ts │ │ │ │ └── RabbitMqEventBus.ts │ │ │ └── shop │ │ │ ├── product_reviews │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductReviewCreator.ts │ │ │ │ └── search_by_product_id │ │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ │ ├── domain │ │ │ │ ├── ProductReview.ts │ │ │ │ ├── ProductReviewComment.ts │ │ │ │ ├── ProductReviewId.ts │ │ │ │ ├── ProductReviewRating.ts │ │ │ │ └── ProductReviewRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductReviewRepository.ts │ │ │ ├── products │ │ │ ├── application │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductFeaturedReview.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRating.ts │ │ │ │ └── ProductRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.ts │ │ │ ├── find │ │ │ │ └── UserFinder.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.ts │ │ │ ├── search │ │ │ │ └── UserSearcher.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.ts │ │ │ ├── domain │ │ │ ├── User.ts │ │ │ ├── UserArchivedDomainEvent.ts │ │ │ ├── UserDoesNotExist.ts │ │ │ ├── UserEmail.ts │ │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ │ ├── UserFinder.ts │ │ │ ├── UserId.ts │ │ │ ├── UserName.ts │ │ │ ├── UserProfilePicture.ts │ │ │ ├── UserRegisteredDomainEvent.ts │ │ │ ├── UserRepository.ts │ │ │ └── UserStatus.ts │ │ │ └── infrastructure │ │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ │ ├── domain │ │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ │ ├── EmailIdMother.ts │ │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── MockEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ │ ├── domain │ │ │ │ └── RetentionUserMother.ts │ │ │ │ └── infrastructure │ │ │ │ └── MockRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── EmailAddressMother.ts │ │ │ │ └── EnumMother.ts │ │ │ └── infrastructure │ │ │ │ ├── MockEventBus.ts │ │ │ │ └── MockUuidGenerator.ts │ │ │ └── shop │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.test.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.test.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.test.ts │ │ │ ├── domain │ │ │ ├── DateMother.ts │ │ │ ├── UserArchivedDomainEventMother.ts │ │ │ ├── UserEmailMother.ts │ │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ │ ├── UserIdMother.ts │ │ │ ├── UserMother.ts │ │ │ ├── UserNameMother.ts │ │ │ ├── UserProfilePictureMother.ts │ │ │ └── UserRegisteredDomainEventMother.ts │ │ │ └── infrastructure │ │ │ └── MockUserRepository.ts │ └── tsconfig.json ├── 2-create_queues_dynamically_in_rabbitmq │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ │ └── http │ │ │ ├── shared │ │ │ └── event_bus-POST.http │ │ │ └── shop │ │ │ ├── product-GET.http │ │ │ ├── product_review-PUT.http │ │ │ ├── product_reviews-GET.http │ │ │ ├── products-GET.http │ │ │ ├── user-GET.http │ │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── codely.svg │ ├── src │ │ ├── app │ │ │ ├── api │ │ │ │ ├── seller_backoffice │ │ │ │ │ └── products │ │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── route.ts │ │ │ │ ├── shared │ │ │ │ │ └── event_bus │ │ │ │ │ │ └── route.ts │ │ │ │ └── shop │ │ │ │ │ ├── product_reviews │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ ├── products │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ └── users │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── scripts │ │ │ │ └── configure-rabbitmq.ts │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── send_welcome_email │ │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ │ ├── domain │ │ │ │ │ ├── Email.ts │ │ │ │ │ ├── EmailBody.ts │ │ │ │ │ ├── EmailId.ts │ │ │ │ │ ├── EmailSender.ts │ │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── FakeEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ │ ├── domain │ │ │ │ ├── RetentionUser.ts │ │ │ │ └── RetentionUserRepository.ts │ │ │ │ └── infrastructure │ │ │ │ └── FakeRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── AggregateRoot.ts │ │ │ │ ├── Collection.ts │ │ │ │ ├── EmailAddress.ts │ │ │ │ ├── Identifier.ts │ │ │ │ ├── Money.ts │ │ │ │ ├── StringValueObject.ts │ │ │ │ ├── UuidGenerator.ts │ │ │ │ └── event │ │ │ │ │ ├── DomainEvent.ts │ │ │ │ │ ├── DomainEventName.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ ├── OfficialUuidGenerator.ts │ │ │ │ ├── dependency_injection │ │ │ │ ├── Subscriber.ts │ │ │ │ └── diod.config.ts │ │ │ │ └── event_bus │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ ├── failover │ │ │ │ └── DomainEventFailover.ts │ │ │ │ └── rabbitmq │ │ │ │ ├── RabbitMqConnection.ts │ │ │ │ └── RabbitMqEventBus.ts │ │ │ └── shop │ │ │ ├── product_reviews │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductReviewCreator.ts │ │ │ │ └── search_by_product_id │ │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ │ ├── domain │ │ │ │ ├── ProductReview.ts │ │ │ │ ├── ProductReviewComment.ts │ │ │ │ ├── ProductReviewId.ts │ │ │ │ ├── ProductReviewRating.ts │ │ │ │ └── ProductReviewRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductReviewRepository.ts │ │ │ ├── products │ │ │ ├── application │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductFeaturedReview.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRating.ts │ │ │ │ └── ProductRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.ts │ │ │ ├── find │ │ │ │ └── UserFinder.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.ts │ │ │ ├── search │ │ │ │ └── UserSearcher.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.ts │ │ │ ├── domain │ │ │ ├── User.ts │ │ │ ├── UserArchivedDomainEvent.ts │ │ │ ├── UserDoesNotExist.ts │ │ │ ├── UserEmail.ts │ │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ │ ├── UserFinder.ts │ │ │ ├── UserId.ts │ │ │ ├── UserName.ts │ │ │ ├── UserProfilePicture.ts │ │ │ ├── UserRegisteredDomainEvent.ts │ │ │ ├── UserRepository.ts │ │ │ └── UserStatus.ts │ │ │ └── infrastructure │ │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ │ ├── domain │ │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ │ ├── EmailIdMother.ts │ │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── MockEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ │ ├── domain │ │ │ │ └── RetentionUserMother.ts │ │ │ │ └── infrastructure │ │ │ │ └── MockRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── EmailAddressMother.ts │ │ │ │ └── EnumMother.ts │ │ │ └── infrastructure │ │ │ │ ├── MockEventBus.ts │ │ │ │ └── MockUuidGenerator.ts │ │ │ └── shop │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.test.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.test.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.test.ts │ │ │ ├── domain │ │ │ ├── DateMother.ts │ │ │ ├── UserArchivedDomainEventMother.ts │ │ │ ├── UserEmailMother.ts │ │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ │ ├── UserIdMother.ts │ │ │ ├── UserMother.ts │ │ │ ├── UserNameMother.ts │ │ │ ├── UserProfilePictureMother.ts │ │ │ └── UserRegisteredDomainEventMother.ts │ │ │ └── infrastructure │ │ │ └── MockUserRepository.ts │ └── tsconfig.json └── 4-queues_with_wildcard_binding │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ └── http │ │ ├── shared │ │ └── event_bus-POST.http │ │ └── shop │ │ ├── product-GET.http │ │ ├── product_review-PUT.http │ │ ├── product_reviews-GET.http │ │ ├── products-GET.http │ │ ├── user-GET.http │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── codely.svg │ ├── src │ ├── app │ │ ├── api │ │ │ ├── seller_backoffice │ │ │ │ └── products │ │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ ├── shared │ │ │ │ └── event_bus │ │ │ │ │ └── route.ts │ │ │ └── shop │ │ │ │ ├── product_reviews │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ ├── products │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ └── users │ │ │ │ └── [id] │ │ │ │ └── route.ts │ │ └── scripts │ │ │ └── configure-rabbitmq.ts │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── send_welcome_email │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ ├── domain │ │ │ │ ├── Email.ts │ │ │ │ ├── EmailBody.ts │ │ │ │ ├── EmailId.ts │ │ │ │ ├── EmailSender.ts │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ └── infrastructure │ │ │ │ └── FakeEmailSender.ts │ │ └── user │ │ │ ├── application │ │ │ └── update_last_activity_date │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ ├── domain │ │ │ ├── RetentionUser.ts │ │ │ └── RetentionUserRepository.ts │ │ │ └── infrastructure │ │ │ └── FakeRetentionUserRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── AggregateRoot.ts │ │ │ ├── Collection.ts │ │ │ ├── EmailAddress.ts │ │ │ ├── Identifier.ts │ │ │ ├── Money.ts │ │ │ ├── StringValueObject.ts │ │ │ ├── UuidGenerator.ts │ │ │ └── event │ │ │ │ ├── DomainEvent.ts │ │ │ │ ├── DomainEventName.ts │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ └── EventBus.ts │ │ └── infrastructure │ │ │ ├── MariaDBConnection.ts │ │ │ ├── OfficialUuidGenerator.ts │ │ │ ├── dependency_injection │ │ │ ├── Subscriber.ts │ │ │ └── diod.config.ts │ │ │ └── event_bus │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ ├── failover │ │ │ └── DomainEventFailover.ts │ │ │ └── rabbitmq │ │ │ ├── RabbitMqConnection.ts │ │ │ └── RabbitMqEventBus.ts │ │ └── shop │ │ ├── product_reviews │ │ ├── application │ │ │ ├── create │ │ │ │ └── ProductReviewCreator.ts │ │ │ └── search_by_product_id │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ ├── domain │ │ │ ├── ProductReview.ts │ │ │ ├── ProductReviewComment.ts │ │ │ ├── ProductReviewId.ts │ │ │ ├── ProductReviewRating.ts │ │ │ └── ProductReviewRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductReviewRepository.ts │ │ ├── products │ │ ├── application │ │ │ ├── search │ │ │ │ └── ProductSearcher.ts │ │ │ └── search_all │ │ │ │ └── AllProductsSearcher.ts │ │ ├── domain │ │ │ ├── Product.ts │ │ │ ├── ProductFeaturedReview.ts │ │ │ ├── ProductId.ts │ │ │ ├── ProductImageUrl.ts │ │ │ ├── ProductImageUrls.ts │ │ │ ├── ProductName.ts │ │ │ ├── ProductRating.ts │ │ │ └── ProductRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductRepository.ts │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.ts │ │ ├── find │ │ │ └── UserFinder.ts │ │ ├── registrar │ │ │ └── UserRegistrar.ts │ │ ├── search │ │ │ └── UserSearcher.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.ts │ │ ├── domain │ │ ├── User.ts │ │ ├── UserArchivedDomainEvent.ts │ │ ├── UserDoesNotExist.ts │ │ ├── UserDomainEvent.ts │ │ ├── UserEmail.ts │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ ├── UserFinder.ts │ │ ├── UserId.ts │ │ ├── UserName.ts │ │ ├── UserProfilePicture.ts │ │ ├── UserRegisteredDomainEvent.ts │ │ ├── UserRepository.ts │ │ └── UserStatus.ts │ │ └── infrastructure │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ ├── domain │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ ├── EmailIdMother.ts │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ └── infrastructure │ │ │ │ └── MockEmailSender.ts │ │ └── user │ │ │ ├── application │ │ │ └── update_last_activity_date │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ ├── domain │ │ │ └── RetentionUserMother.ts │ │ │ └── infrastructure │ │ │ └── MockRetentionUserRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── EmailAddressMother.ts │ │ │ └── EnumMother.ts │ │ └── infrastructure │ │ │ ├── MockEventBus.ts │ │ │ └── MockUuidGenerator.ts │ │ └── shop │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.test.ts │ │ ├── registrar │ │ │ └── UserRegistrar.test.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.test.ts │ │ ├── domain │ │ ├── DateMother.ts │ │ ├── UserArchivedDomainEventMother.ts │ │ ├── UserEmailMother.ts │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ ├── UserIdMother.ts │ │ ├── UserMother.ts │ │ ├── UserNameMother.ts │ │ ├── UserProfilePictureMother.ts │ │ └── UserRegisteredDomainEventMother.ts │ │ └── infrastructure │ │ └── MockUserRepository.ts │ └── tsconfig.json ├── 05-add_resilience ├── 1-consume_events │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ │ └── http │ │ │ ├── shared │ │ │ └── event_bus-POST.http │ │ │ └── shop │ │ │ ├── product-GET.http │ │ │ ├── product_review-PUT.http │ │ │ ├── product_reviews-GET.http │ │ │ ├── products-GET.http │ │ │ ├── user-GET.http │ │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── codely.svg │ ├── src │ │ ├── app │ │ │ ├── api │ │ │ │ ├── seller_backoffice │ │ │ │ │ └── products │ │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── route.ts │ │ │ │ ├── shared │ │ │ │ │ └── event_bus │ │ │ │ │ │ └── route.ts │ │ │ │ └── shop │ │ │ │ │ ├── product_reviews │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ ├── products │ │ │ │ │ ├── [id] │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ │ └── users │ │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── scripts │ │ │ │ ├── configure-rabbitmq.ts │ │ │ │ └── consume-rabbitmq.ts │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── send_welcome_email │ │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ │ ├── domain │ │ │ │ │ ├── Email.ts │ │ │ │ │ ├── EmailBody.ts │ │ │ │ │ ├── EmailId.ts │ │ │ │ │ ├── EmailSender.ts │ │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── FakeEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ │ ├── domain │ │ │ │ ├── RetentionUser.ts │ │ │ │ └── RetentionUserRepository.ts │ │ │ │ └── infrastructure │ │ │ │ └── MySqlRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── AggregateRoot.ts │ │ │ │ ├── Collection.ts │ │ │ │ ├── EmailAddress.ts │ │ │ │ ├── Identifier.ts │ │ │ │ ├── Money.ts │ │ │ │ ├── StringValueObject.ts │ │ │ │ ├── UuidGenerator.ts │ │ │ │ └── event │ │ │ │ │ ├── DomainEvent.ts │ │ │ │ │ ├── DomainEventClass.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ ├── OfficialUuidGenerator.ts │ │ │ │ ├── dependency_injection │ │ │ │ ├── Subscriber.ts │ │ │ │ └── diod.config.ts │ │ │ │ └── event_bus │ │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ ├── failover │ │ │ │ └── DomainEventFailover.ts │ │ │ │ └── rabbitmq │ │ │ │ ├── RabbitMqConnection.ts │ │ │ │ └── RabbitMqEventBus.ts │ │ │ └── shop │ │ │ ├── product_reviews │ │ │ ├── application │ │ │ │ ├── create │ │ │ │ │ └── ProductReviewCreator.ts │ │ │ │ └── search_by_product_id │ │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ │ ├── domain │ │ │ │ ├── ProductReview.ts │ │ │ │ ├── ProductReviewComment.ts │ │ │ │ ├── ProductReviewId.ts │ │ │ │ ├── ProductReviewRating.ts │ │ │ │ └── ProductReviewRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductReviewRepository.ts │ │ │ ├── products │ │ │ ├── application │ │ │ │ ├── search │ │ │ │ │ └── ProductSearcher.ts │ │ │ │ └── search_all │ │ │ │ │ └── AllProductsSearcher.ts │ │ │ ├── domain │ │ │ │ ├── Product.ts │ │ │ │ ├── ProductFeaturedReview.ts │ │ │ │ ├── ProductId.ts │ │ │ │ ├── ProductImageUrl.ts │ │ │ │ ├── ProductImageUrls.ts │ │ │ │ ├── ProductName.ts │ │ │ │ ├── ProductRating.ts │ │ │ │ └── ProductRepository.ts │ │ │ └── infrastructure │ │ │ │ └── MySqlProductRepository.ts │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.ts │ │ │ ├── find │ │ │ │ └── UserFinder.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.ts │ │ │ ├── search │ │ │ │ └── UserSearcher.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.ts │ │ │ ├── domain │ │ │ ├── User.ts │ │ │ ├── UserArchivedDomainEvent.ts │ │ │ ├── UserDoesNotExist.ts │ │ │ ├── UserDomainEvent.ts │ │ │ ├── UserEmail.ts │ │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ │ ├── UserFinder.ts │ │ │ ├── UserId.ts │ │ │ ├── UserName.ts │ │ │ ├── UserProfilePicture.ts │ │ │ ├── UserRegisteredDomainEvent.ts │ │ │ ├── UserRepository.ts │ │ │ └── UserStatus.ts │ │ │ └── infrastructure │ │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ │ └── contexts │ │ │ ├── retention │ │ │ ├── email │ │ │ │ ├── application │ │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ │ ├── domain │ │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ │ ├── EmailIdMother.ts │ │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ │ └── infrastructure │ │ │ │ │ └── MockEmailSender.ts │ │ │ └── user │ │ │ │ ├── application │ │ │ │ └── update_last_activity_date │ │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ │ ├── domain │ │ │ │ └── RetentionUserMother.ts │ │ │ │ └── infrastructure │ │ │ │ └── MockRetentionUserRepository.ts │ │ │ ├── shared │ │ │ ├── domain │ │ │ │ ├── EmailAddressMother.ts │ │ │ │ └── EnumMother.ts │ │ │ └── infrastructure │ │ │ │ ├── MockEventBus.ts │ │ │ │ └── MockUuidGenerator.ts │ │ │ └── shop │ │ │ └── users │ │ │ ├── application │ │ │ ├── archive │ │ │ │ └── UserArchiver.test.ts │ │ │ ├── registrar │ │ │ │ └── UserRegistrar.test.ts │ │ │ └── update_email │ │ │ │ └── UserEmailUpdater.test.ts │ │ │ ├── domain │ │ │ ├── DateMother.ts │ │ │ ├── UserArchivedDomainEventMother.ts │ │ │ ├── UserEmailMother.ts │ │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ │ ├── UserIdMother.ts │ │ │ ├── UserMother.ts │ │ │ ├── UserNameMother.ts │ │ │ ├── UserProfilePictureMother.ts │ │ │ └── UserRegisteredDomainEventMother.ts │ │ │ └── infrastructure │ │ │ └── MockUserRepository.ts │ └── tsconfig.json └── 3-implement_retry_dead_letter │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── databases │ └── ecommerce.sql │ ├── docker-compose.yml │ ├── etc │ └── http │ │ ├── shared │ │ └── event_bus-POST.http │ │ └── shop │ │ ├── product-GET.http │ │ ├── product_review-PUT.http │ │ ├── product_reviews-GET.http │ │ ├── products-GET.http │ │ ├── user-GET.http │ │ └── user-PUT.http │ ├── jest.config.js │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── codely.svg │ ├── src │ ├── app │ │ ├── api │ │ │ ├── seller_backoffice │ │ │ │ └── products │ │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ ├── shared │ │ │ │ └── event_bus │ │ │ │ │ └── route.ts │ │ │ └── shop │ │ │ │ ├── product_reviews │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ ├── products │ │ │ │ ├── [id] │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ └── users │ │ │ │ └── [id] │ │ │ │ └── route.ts │ │ └── scripts │ │ │ ├── configure-rabbitmq.ts │ │ │ └── consume-rabbitmq.ts │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── send_welcome_email │ │ │ │ │ ├── SendWelcomeEmailOnUserRegistered.ts │ │ │ │ │ └── WelcomeEmailSender.ts │ │ │ ├── domain │ │ │ │ ├── Email.ts │ │ │ │ ├── EmailBody.ts │ │ │ │ ├── EmailId.ts │ │ │ │ ├── EmailSender.ts │ │ │ │ ├── WelcomeEmail.ts │ │ │ │ └── WelcomeEmailSentDomainEvent.ts │ │ │ └── infrastructure │ │ │ │ └── FakeEmailSender.ts │ │ └── user │ │ │ ├── application │ │ │ └── update_last_activity_date │ │ │ │ ├── UpdateLastActivityDateOnUserUpdated.ts │ │ │ │ └── UserLastActivityUpdater.ts │ │ │ ├── domain │ │ │ ├── RetentionUser.ts │ │ │ └── RetentionUserRepository.ts │ │ │ └── infrastructure │ │ │ └── MySqlRetentionUserRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── AggregateRoot.ts │ │ │ ├── Collection.ts │ │ │ ├── EmailAddress.ts │ │ │ ├── Identifier.ts │ │ │ ├── Money.ts │ │ │ ├── StringValueObject.ts │ │ │ ├── UuidGenerator.ts │ │ │ └── event │ │ │ │ ├── DomainEvent.ts │ │ │ │ ├── DomainEventClass.ts │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ └── EventBus.ts │ │ └── infrastructure │ │ │ ├── MariaDBConnection.ts │ │ │ ├── OfficialUuidGenerator.ts │ │ │ ├── dependency_injection │ │ │ ├── Subscriber.ts │ │ │ └── diod.config.ts │ │ │ └── event_bus │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ ├── failover │ │ │ └── DomainEventFailover.ts │ │ │ └── rabbitmq │ │ │ ├── RabbitMqConnection.ts │ │ │ └── RabbitMqEventBus.ts │ │ └── shop │ │ ├── product_reviews │ │ ├── application │ │ │ ├── create │ │ │ │ └── ProductReviewCreator.ts │ │ │ └── search_by_product_id │ │ │ │ └── ProductReviewsByProductSearcher.ts │ │ ├── domain │ │ │ ├── ProductReview.ts │ │ │ ├── ProductReviewComment.ts │ │ │ ├── ProductReviewId.ts │ │ │ ├── ProductReviewRating.ts │ │ │ └── ProductReviewRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductReviewRepository.ts │ │ ├── products │ │ ├── application │ │ │ ├── search │ │ │ │ └── ProductSearcher.ts │ │ │ └── search_all │ │ │ │ └── AllProductsSearcher.ts │ │ ├── domain │ │ │ ├── Product.ts │ │ │ ├── ProductFeaturedReview.ts │ │ │ ├── ProductId.ts │ │ │ ├── ProductImageUrl.ts │ │ │ ├── ProductImageUrls.ts │ │ │ ├── ProductName.ts │ │ │ ├── ProductRating.ts │ │ │ └── ProductRepository.ts │ │ └── infrastructure │ │ │ └── MySqlProductRepository.ts │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.ts │ │ ├── find │ │ │ └── UserFinder.ts │ │ ├── registrar │ │ │ └── UserRegistrar.ts │ │ ├── search │ │ │ └── UserSearcher.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.ts │ │ ├── domain │ │ ├── User.ts │ │ ├── UserArchivedDomainEvent.ts │ │ ├── UserDoesNotExist.ts │ │ ├── UserDomainEvent.ts │ │ ├── UserEmail.ts │ │ ├── UserEmailUpdatedDomainEvent.ts │ │ ├── UserFinder.ts │ │ ├── UserId.ts │ │ ├── UserName.ts │ │ ├── UserProfilePicture.ts │ │ ├── UserRegisteredDomainEvent.ts │ │ ├── UserRepository.ts │ │ └── UserStatus.ts │ │ └── infrastructure │ │ └── MySqlUserRepository.ts │ ├── tailwind.config.ts │ ├── tests │ └── contexts │ │ ├── retention │ │ ├── email │ │ │ ├── application │ │ │ │ └── SendWelcomeEmailOnUserRegistered.test.ts │ │ │ ├── domain │ │ │ │ ├── EmailBodyMother.ts │ │ │ │ ├── EmailIdMother.ts │ │ │ │ ├── WelcomeEmailMother.ts │ │ │ │ └── WelcomeEmailSentDomainEventMother.ts │ │ │ └── infrastructure │ │ │ │ └── MockEmailSender.ts │ │ └── user │ │ │ ├── application │ │ │ └── update_last_activity_date │ │ │ │ └── UpdateLastActivityDateOnUserUpdated.test.ts │ │ │ ├── domain │ │ │ └── RetentionUserMother.ts │ │ │ └── infrastructure │ │ │ └── MockRetentionUserRepository.ts │ │ ├── shared │ │ ├── domain │ │ │ ├── EmailAddressMother.ts │ │ │ └── EnumMother.ts │ │ └── infrastructure │ │ │ ├── MockEventBus.ts │ │ │ └── MockUuidGenerator.ts │ │ └── shop │ │ └── users │ │ ├── application │ │ ├── archive │ │ │ └── UserArchiver.test.ts │ │ ├── registrar │ │ │ └── UserRegistrar.test.ts │ │ └── update_email │ │ │ └── UserEmailUpdater.test.ts │ │ ├── domain │ │ ├── DateMother.ts │ │ ├── UserArchivedDomainEventMother.ts │ │ ├── UserEmailMother.ts │ │ ├── UserEmailUpdatedDomainEventMother.ts │ │ ├── UserIdMother.ts │ │ ├── UserMother.ts │ │ ├── UserNameMother.ts │ │ ├── UserProfilePictureMother.ts │ │ └── UserRegisteredDomainEventMother.ts │ │ └── infrastructure │ │ └── MockUserRepository.ts │ └── tsconfig.json ├── 06-rabbitmq_particularities └── 1-dynamic_consumers_in_php │ ├── .env │ ├── .github │ ├── FUNDING.yml │ └── workflows │ │ ├── ci.yml │ │ └── labeler.yml │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── apps │ ├── backoffice │ │ ├── backend │ │ │ ├── bin │ │ │ │ └── console │ │ │ ├── config │ │ │ │ ├── bundles.php │ │ │ │ ├── routes │ │ │ │ │ ├── courses.yaml │ │ │ │ │ ├── health-check.yaml │ │ │ │ │ └── metrics.yaml │ │ │ │ ├── services.yaml │ │ │ │ ├── services │ │ │ │ │ └── framework.yaml │ │ │ │ └── services_test.yaml │ │ │ ├── public │ │ │ │ └── index.php │ │ │ └── src │ │ │ │ ├── BackofficeBackendKernel.php │ │ │ │ └── Controller │ │ │ │ ├── Courses │ │ │ │ └── CoursesGetController.php │ │ │ │ ├── HealthCheck │ │ │ │ └── HealthCheckGetController.php │ │ │ │ └── Metrics │ │ │ │ └── MetricsController.php │ │ └── frontend │ │ │ ├── bin │ │ │ └── console │ │ │ ├── config │ │ │ ├── bundles.php │ │ │ ├── routes │ │ │ │ ├── api_courses.yaml │ │ │ │ ├── courses.yaml │ │ │ │ ├── health-check.yaml │ │ │ │ ├── home.yaml │ │ │ │ └── metrics.yaml │ │ │ ├── services.yaml │ │ │ ├── services │ │ │ │ └── framework.yaml │ │ │ └── services_test.yaml │ │ │ ├── public │ │ │ ├── images │ │ │ │ └── logo.png │ │ │ └── index.php │ │ │ ├── src │ │ │ ├── BackofficeFrontendKernel.php │ │ │ ├── Command │ │ │ │ └── ImportCoursesToElasticsearchCommand.php │ │ │ └── Controller │ │ │ │ ├── Courses │ │ │ │ ├── CoursesGetWebController.php │ │ │ │ └── CoursesPostWebController.php │ │ │ │ ├── HealthCheck │ │ │ │ └── HealthCheckGetController.php │ │ │ │ ├── Home │ │ │ │ └── HomeGetWebController.php │ │ │ │ └── Metrics │ │ │ │ └── MetricsController.php │ │ │ └── templates │ │ │ ├── master.html.twig │ │ │ ├── pages │ │ │ ├── courses │ │ │ │ ├── courses.html.twig │ │ │ │ └── partials │ │ │ │ │ ├── list_courses.html.twig │ │ │ │ │ └── new_course_form.html.twig │ │ │ └── home.html.twig │ │ │ └── partials │ │ │ ├── footer.html.twig │ │ │ └── header.html.twig │ ├── bootstrap.php │ └── mooc │ │ ├── backend │ │ ├── bin │ │ │ └── console │ │ ├── config │ │ │ ├── bundles.php │ │ │ ├── routes │ │ │ │ ├── courses.yaml │ │ │ │ ├── courses_counter.yaml │ │ │ │ ├── health-check.yaml │ │ │ │ └── metrics.yaml │ │ │ ├── services.yaml │ │ │ ├── services │ │ │ │ └── framework.yaml │ │ │ └── services_test.yaml │ │ ├── public │ │ │ └── index.php │ │ ├── src │ │ │ ├── Command │ │ │ │ └── DomainEvents │ │ │ │ │ ├── MySql │ │ │ │ │ └── ConsumeMySqlDomainEventsCommand.php │ │ │ │ │ ├── PublishDomainEventsFromMutationsCommand.php │ │ │ │ │ └── RabbitMq │ │ │ │ │ ├── ConfigureRabbitMqCommand.php │ │ │ │ │ ├── ConsumeRabbitMqDomainEventsCommand.php │ │ │ │ │ └── GenerateSupervisorRabbitMqConsumerFilesCommand.php │ │ │ ├── Controller │ │ │ │ ├── Courses │ │ │ │ │ └── CoursesPutController.php │ │ │ │ ├── CoursesCounter │ │ │ │ │ └── CoursesCounterGetController.php │ │ │ │ ├── HealthCheck │ │ │ │ │ └── HealthCheckGetController.php │ │ │ │ └── Metrics │ │ │ │ │ └── MetricsController.php │ │ │ └── MoocBackendKernel.php │ │ └── tests │ │ │ ├── features │ │ │ ├── courses │ │ │ │ └── course_put.feature │ │ │ ├── courses_counter │ │ │ │ └── courses_counter_get.feature │ │ │ └── health_check │ │ │ │ └── health_check_get.feature │ │ │ └── mooc_backend.yml │ │ └── frontend │ │ └── src │ │ └── .gitkeep │ ├── behat.yml │ ├── composer.json │ ├── composer.lock │ ├── docker-compose.yml │ ├── ecs.php │ ├── etc │ ├── databases │ │ ├── backoffice │ │ │ └── courses.json │ │ └── mooc.sql │ ├── endpoints │ │ ├── backoffice_frontend.http │ │ └── mooc_backend.http │ ├── infrastructure │ │ └── php │ │ │ ├── conf.d │ │ │ ├── apcu.ini │ │ │ ├── opcache.ini │ │ │ └── xdebug.ini │ │ │ └── php.ini │ └── prometheus │ │ └── prometheus.yml │ ├── phpmd.xml │ ├── phpstan.neon │ ├── phpunit.xml │ ├── psalm.xml │ ├── rector.php │ ├── src │ ├── Analytics │ │ └── DomainEvents │ │ │ ├── Application │ │ │ └── Store │ │ │ │ ├── DomainEventStorer.php │ │ │ │ └── StoreDomainEventOnOccurred.php │ │ │ └── Domain │ │ │ ├── AnalyticsDomainEvent.php │ │ │ ├── AnalyticsDomainEventAggregateId.php │ │ │ ├── AnalyticsDomainEventBody.php │ │ │ ├── AnalyticsDomainEventId.php │ │ │ ├── AnalyticsDomainEventName.php │ │ │ └── DomainEventsRepository.php │ ├── Backoffice │ │ ├── Auth │ │ │ ├── Application │ │ │ │ └── Authenticate │ │ │ │ │ ├── AuthenticateUserCommand.php │ │ │ │ │ ├── AuthenticateUserCommandHandler.php │ │ │ │ │ └── UserAuthenticator.php │ │ │ ├── Domain │ │ │ │ ├── AuthPassword.php │ │ │ │ ├── AuthRepository.php │ │ │ │ ├── AuthUser.php │ │ │ │ ├── AuthUsername.php │ │ │ │ ├── InvalidAuthCredentials.php │ │ │ │ └── InvalidAuthUsername.php │ │ │ └── Infrastructure │ │ │ │ └── Persistence │ │ │ │ └── InMemoryAuthRepository.php │ │ ├── Courses │ │ │ ├── Application │ │ │ │ ├── BackofficeCourseResponse.php │ │ │ │ ├── BackofficeCoursesResponse.php │ │ │ │ ├── Create │ │ │ │ │ ├── BackofficeCourseCreator.php │ │ │ │ │ └── CreateBackofficeCourseOnCourseCreated.php │ │ │ │ ├── SearchAll │ │ │ │ │ ├── AllBackofficeCoursesSearcher.php │ │ │ │ │ ├── SearchAllBackofficeCoursesQuery.php │ │ │ │ │ └── SearchAllBackofficeCoursesQueryHandler.php │ │ │ │ └── SearchByCriteria │ │ │ │ │ ├── BackofficeCoursesByCriteriaSearcher.php │ │ │ │ │ ├── SearchBackofficeCoursesByCriteriaQuery.php │ │ │ │ │ └── SearchBackofficeCoursesByCriteriaQueryHandler.php │ │ │ ├── Domain │ │ │ │ ├── BackofficeCourse.php │ │ │ │ └── BackofficeCourseRepository.php │ │ │ └── Infrastructure │ │ │ │ └── Persistence │ │ │ │ ├── Doctrine │ │ │ │ └── BackofficeCourse.orm.xml │ │ │ │ ├── ElasticsearchBackofficeCourseRepository.php │ │ │ │ ├── InMemoryCacheBackofficeCourseRepository.php │ │ │ │ └── MySqlBackofficeCourseRepository.php │ │ └── Shared │ │ │ └── Infrastructure │ │ │ └── Symfony │ │ │ └── DependencyInjection │ │ │ └── backoffice_services.yaml │ ├── Mooc │ │ ├── Courses │ │ │ ├── Application │ │ │ │ ├── Create │ │ │ │ │ ├── CourseCreator.php │ │ │ │ │ ├── CreateCourseCommand.php │ │ │ │ │ └── CreateCourseCommandHandler.php │ │ │ │ ├── Find │ │ │ │ │ └── CourseFinder.php │ │ │ │ └── Update │ │ │ │ │ └── CourseRenamer.php │ │ │ ├── Domain │ │ │ │ ├── Course.php │ │ │ │ ├── CourseCreatedDomainEvent.php │ │ │ │ ├── CourseDuration.php │ │ │ │ ├── CourseName.php │ │ │ │ ├── CourseNotExist.php │ │ │ │ └── CourseRepository.php │ │ │ └── Infrastructure │ │ │ │ ├── Cdc │ │ │ │ └── DatabaseMutationToCourseCreatedDomainEvent.php │ │ │ │ └── Persistence │ │ │ │ ├── Doctrine │ │ │ │ ├── Course.orm.xml │ │ │ │ ├── CourseDuration.orm.xml │ │ │ │ ├── CourseIdType.php │ │ │ │ └── CourseName.orm.xml │ │ │ │ ├── DoctrineCourseRepository.php │ │ │ │ └── FileCourseRepository.php │ │ ├── CoursesCounter │ │ │ ├── Application │ │ │ │ ├── Find │ │ │ │ │ ├── CoursesCounterFinder.php │ │ │ │ │ ├── CoursesCounterResponse.php │ │ │ │ │ ├── FindCoursesCounterQuery.php │ │ │ │ │ └── FindCoursesCounterQueryHandler.php │ │ │ │ └── Increment │ │ │ │ │ ├── CoursesCounterIncrementer.php │ │ │ │ │ └── IncrementCoursesCounterOnCourseCreated.php │ │ │ ├── Domain │ │ │ │ ├── CoursesCounter.php │ │ │ │ ├── CoursesCounterId.php │ │ │ │ ├── CoursesCounterIncrementedDomainEvent.php │ │ │ │ ├── CoursesCounterNotExist.php │ │ │ │ ├── CoursesCounterRepository.php │ │ │ │ └── CoursesCounterTotal.php │ │ │ └── Infrastructure │ │ │ │ └── Persistence │ │ │ │ ├── Doctrine │ │ │ │ ├── CourseCounterIdType.php │ │ │ │ ├── CourseIdsType.php │ │ │ │ ├── CoursesCounter.orm.xml │ │ │ │ └── CoursesCounterTotal.orm.xml │ │ │ │ └── DoctrineCoursesCounterRepository.php │ │ ├── Notifications │ │ │ └── Application │ │ │ │ ├── SendNewCommentReplyEmail │ │ │ │ └── .gitkeep │ │ │ │ ├── SendNewCommentReplyPush │ │ │ │ └── .gitkeep │ │ │ │ └── SendResetPasswordEmail │ │ │ │ └── .gitkeep │ │ ├── Shared │ │ │ ├── Domain │ │ │ │ ├── Courses │ │ │ │ │ └── CourseId.php │ │ │ │ └── Videos │ │ │ │ │ └── VideoUrl.php │ │ │ └── Infrastructure │ │ │ │ ├── Doctrine │ │ │ │ ├── DbalTypesSearcher.php │ │ │ │ ├── DoctrinePrefixesSearcher.php │ │ │ │ └── MoocEntityManagerFactory.php │ │ │ │ └── Symfony │ │ │ │ └── DependencyInjection │ │ │ │ └── mooc_services.yaml │ │ ├── Steps │ │ │ ├── Application │ │ │ │ └── Create │ │ │ │ │ ├── CreateVideoStepCommandHandler.php │ │ │ │ │ └── VideoStepCreator.php │ │ │ ├── Domain │ │ │ │ ├── Exercise │ │ │ │ │ ├── ExerciseStep.php │ │ │ │ │ └── ExerciseStepContent.php │ │ │ │ ├── Quiz │ │ │ │ │ ├── QuizStep.php │ │ │ │ │ └── QuizStepQuestion.php │ │ │ │ ├── Step.php │ │ │ │ ├── StepDuration.php │ │ │ │ ├── StepId.php │ │ │ │ ├── StepRepository.php │ │ │ │ ├── StepTitle.php │ │ │ │ └── Video │ │ │ │ │ ├── VideoStep.php │ │ │ │ │ └── VideoStepUrl.php │ │ │ └── Infrastructure │ │ │ │ └── Persistence │ │ │ │ ├── Doctrine │ │ │ │ ├── Exercise.ExerciseStep.orm.xml │ │ │ │ ├── Exercise.ExerciseStepContent.orm.xml │ │ │ │ ├── Quiz.QuizStep.orm.xml │ │ │ │ ├── QuizStepQuestionsType.php │ │ │ │ ├── Step.orm.xml │ │ │ │ ├── StepDuration.orm.xml │ │ │ │ ├── StepIdType.php │ │ │ │ ├── StepTitle.orm.xml │ │ │ │ ├── Video.VideoStep.orm.xml │ │ │ │ └── Video.VideoStepUrl.orm.xml │ │ │ │ └── MySqlStepRepository.php │ │ └── Videos │ │ │ ├── Application │ │ │ ├── Create │ │ │ │ ├── CreateVideoCommand.php │ │ │ │ ├── CreateVideoCommandHandler.php │ │ │ │ └── VideoCreator.php │ │ │ ├── Find │ │ │ │ ├── FindVideoQuery.php │ │ │ │ ├── FindVideoQueryHandler.php │ │ │ │ ├── VideoFinder.php │ │ │ │ ├── VideoResponse.php │ │ │ │ └── VideoResponseConverter.php │ │ │ ├── Trim │ │ │ │ ├── TrimVideoCommand.php │ │ │ │ ├── TrimVideoCommandHandler.php │ │ │ │ └── VideoTrimmer.php │ │ │ └── Update │ │ │ │ └── VideoTitleUpdater.php │ │ │ ├── Domain │ │ │ ├── Video.php │ │ │ ├── VideoCreatedDomainEvent.php │ │ │ ├── VideoFinder.php │ │ │ ├── VideoId.php │ │ │ ├── VideoNotFound.php │ │ │ ├── VideoRepository.php │ │ │ ├── VideoTitle.php │ │ │ ├── VideoType.php │ │ │ └── Videos.php │ │ │ └── Infrastructure │ │ │ └── Persistence │ │ │ ├── Doctrine │ │ │ ├── Video.orm.xml │ │ │ ├── VideoIdType.php │ │ │ ├── VideoTitle.orm.xml │ │ │ └── VideoType.orm.xml │ │ │ └── VideoRepositoryMySql.php │ ├── Retention │ │ ├── Campaign │ │ │ ├── Application │ │ │ │ ├── NewCourseAvailable │ │ │ │ │ ├── Schedule │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ └── Trigger │ │ │ │ │ │ └── .gitkeep │ │ │ │ └── WelcomeUser │ │ │ │ │ └── Trigger │ │ │ │ │ └── .gitkeep │ │ │ ├── Domain │ │ │ │ └── .gitkeep │ │ │ └── Infrastructure │ │ │ │ └── .gitkeep │ │ ├── Email │ │ │ ├── Application │ │ │ │ ├── SendNewCourseAvailable │ │ │ │ │ └── .gitkeep │ │ │ │ └── SendWelcomeUser │ │ │ │ │ └── .gitkeep │ │ │ ├── Domain │ │ │ │ └── .gitkeep │ │ │ └── Infrastructure │ │ │ │ └── .gitkeep │ │ ├── Push │ │ │ ├── Application │ │ │ │ └── SendNewCourseAvailable │ │ │ │ │ └── .gitkeep │ │ │ ├── Domain │ │ │ │ └── .gitkeep │ │ │ └── Infrastructure │ │ │ │ └── .gitkeep │ │ └── Sms │ │ │ ├── Application │ │ │ └── .gitkeep │ │ │ ├── Domain │ │ │ └── .gitkeep │ │ │ └── Infrastructure │ │ │ └── .gitkeep │ └── Shared │ │ ├── Domain │ │ ├── Aggregate │ │ │ └── AggregateRoot.php │ │ ├── Assert.php │ │ ├── Bus │ │ │ ├── Command │ │ │ │ ├── Command.php │ │ │ │ ├── CommandBus.php │ │ │ │ └── CommandHandler.php │ │ │ ├── Event │ │ │ │ ├── DomainEvent.php │ │ │ │ ├── DomainEventSubscriber.php │ │ │ │ └── EventBus.php │ │ │ └── Query │ │ │ │ ├── Query.php │ │ │ │ ├── QueryBus.php │ │ │ │ ├── QueryHandler.php │ │ │ │ └── Response.php │ │ ├── Collection.php │ │ ├── Criteria │ │ │ ├── Criteria.php │ │ │ ├── Filter.php │ │ │ ├── FilterField.php │ │ │ ├── FilterOperator.php │ │ │ ├── FilterValue.php │ │ │ ├── Filters.php │ │ │ ├── Order.php │ │ │ ├── OrderBy.php │ │ │ └── OrderType.php │ │ ├── DomainError.php │ │ ├── Logger.php │ │ ├── Monitoring.php │ │ ├── RandomNumberGenerator.php │ │ ├── Second.php │ │ ├── SecondsInterval.php │ │ ├── Utils.php │ │ ├── UuidGenerator.php │ │ └── ValueObject │ │ │ ├── IntValueObject.php │ │ │ ├── SimpleUuid.php │ │ │ ├── StringValueObject.php │ │ │ └── Uuid.php │ │ └── Infrastructure │ │ ├── Bus │ │ ├── CallableFirstParameterExtractor.php │ │ ├── Command │ │ │ ├── CommandNotRegisteredError.php │ │ │ └── InMemorySymfonyCommandBus.php │ │ ├── Event │ │ │ ├── DomainEventJsonDeserializer.php │ │ │ ├── DomainEventJsonSerializer.php │ │ │ ├── DomainEventMapping.php │ │ │ ├── DomainEventSubscriberLocator.php │ │ │ ├── InMemory │ │ │ │ └── InMemorySymfonyEventBus.php │ │ │ ├── MySql │ │ │ │ ├── MySqlDoctrineDomainEventsConsumer.php │ │ │ │ └── MySqlDoctrineEventBus.php │ │ │ ├── RabbitMq │ │ │ │ ├── RabbitMqConfigurer.php │ │ │ │ ├── RabbitMqConnection.php │ │ │ │ ├── RabbitMqDomainEventsConsumer.php │ │ │ │ ├── RabbitMqEventBus.php │ │ │ │ ├── RabbitMqExchangeNameFormatter.php │ │ │ │ └── RabbitMqQueueNameFormatter.php │ │ │ └── WithMonitoring │ │ │ │ └── WithPrometheusMonitoringEventBus.php │ │ └── Query │ │ │ ├── InMemorySymfonyQueryBus.php │ │ │ └── QueryNotRegisteredError.php │ │ ├── Cdc │ │ ├── DatabaseMutationAction.php │ │ └── DatabaseMutationToDomainEvent.php │ │ ├── Doctrine │ │ ├── DatabaseConnections.php │ │ ├── Dbal │ │ │ ├── DbalCustomTypesRegistrar.php │ │ │ └── DoctrineCustomType.php │ │ └── DoctrineEntityManagerFactory.php │ │ ├── Elasticsearch │ │ ├── ElasticsearchClient.php │ │ └── ElasticsearchClientFactory.php │ │ ├── Logger │ │ └── MonologLogger.php │ │ ├── Monitoring │ │ └── PrometheusMonitor.php │ │ ├── Persistence │ │ ├── Doctrine │ │ │ ├── DoctrineCriteriaConverter.php │ │ │ ├── DoctrineRepository.php │ │ │ └── UuidType.php │ │ └── Elasticsearch │ │ │ ├── ElasticQueryGenerator.php │ │ │ ├── ElasticsearchCriteriaConverter.php │ │ │ └── ElasticsearchRepository.php │ │ ├── PhpRandomNumberGenerator.php │ │ ├── RamseyUuidGenerator.php │ │ └── Symfony │ │ ├── AddJsonBodyToRequestListener.php │ │ ├── ApiController.php │ │ ├── ApiExceptionListener.php │ │ ├── ApiExceptionsHttpStatusCodeMapping.php │ │ ├── BasicHttpAuthMiddleware.php │ │ ├── FlashSession.php │ │ └── WebController.php │ └── tests │ ├── Backoffice │ ├── Auth │ │ ├── Application │ │ │ └── Authenticate │ │ │ │ ├── AuthenticateUserCommandHandlerTest.php │ │ │ │ └── AuthenticateUserCommandMother.php │ │ ├── AuthModuleUnitTestCase.php │ │ └── Domain │ │ │ ├── AuthPasswordMother.php │ │ │ ├── AuthUserMother.php │ │ │ └── AuthUsernameMother.php │ ├── Courses │ │ ├── BackofficeCoursesModuleInfrastructureTestCase.php │ │ ├── Domain │ │ │ ├── BackofficeCourseCriteriaMother.php │ │ │ └── BackofficeCourseMother.php │ │ └── Infrastructure │ │ │ └── Persistence │ │ │ ├── ElasticsearchBackofficeCourseRepositoryTest.php │ │ │ └── MySqlBackofficeCourseRepositoryTest.php │ └── Shared │ │ └── Infraestructure │ │ └── PhpUnit │ │ ├── BackofficeContextInfrastructureTestCase.php │ │ └── BackofficeEnvironmentArranger.php │ ├── Mooc │ ├── Courses │ │ ├── Application │ │ │ ├── Create │ │ │ │ ├── CreateCourseCommandHandlerTest.php │ │ │ │ └── CreateCourseCommandMother.php │ │ │ └── Update │ │ │ │ └── CourseRenamerTest.php │ │ ├── CoursesModuleInfrastructureTestCase.php │ │ ├── CoursesModuleUnitTestCase.php │ │ ├── Domain │ │ │ ├── CourseCreatedDomainEventMother.php │ │ │ ├── CourseDurationMother.php │ │ │ ├── CourseIdMother.php │ │ │ ├── CourseMother.php │ │ │ └── CourseNameMother.php │ │ └── Infrastructure │ │ │ └── Persistence │ │ │ └── CourseRepositoryTest.php │ ├── CoursesCounter │ │ ├── Application │ │ │ ├── Find │ │ │ │ ├── CoursesCounterResponseMother.php │ │ │ │ └── FindCoursesCounterQueryHandlerTest.php │ │ │ └── Increment │ │ │ │ └── IncrementCoursesCounterOnCourseCreatedTest.php │ │ ├── CoursesCounterModuleUnitTestCase.php │ │ └── Domain │ │ │ ├── CoursesCounterIdMother.php │ │ │ ├── CoursesCounterIncrementedDomainEventMother.php │ │ │ ├── CoursesCounterMother.php │ │ │ └── CoursesCounterTotalMother.php │ ├── MoocArchitectureTest.php │ ├── Shared │ │ ├── Domain │ │ │ └── .gitkeep │ │ └── Infrastructure │ │ │ └── PhpUnit │ │ │ ├── MoocContextInfrastructureTestCase.php │ │ │ └── MoocEnvironmentArranger.php │ ├── Steps │ │ ├── Domain │ │ │ ├── Exercise │ │ │ │ ├── ExerciseStepContentMother.php │ │ │ │ └── ExerciseStepMother.php │ │ │ ├── Quiz │ │ │ │ ├── QuizStepMother.php │ │ │ │ └── QuizStepQuestionMother.php │ │ │ ├── StepDurationMother.php │ │ │ ├── StepIdMother.php │ │ │ ├── StepTitleMother.php │ │ │ └── Video │ │ │ │ ├── VideoStepMother.php │ │ │ │ └── VideoStepUrlMother.php │ │ ├── Infrastructure │ │ │ └── Persistence │ │ │ │ └── MySqlStepRepositoryTest.php │ │ └── StepsModuleInfrastructureTestCase.php │ └── Videos │ │ ├── Application │ │ └── .gitkeep │ │ ├── Domain │ │ └── .gitkeep │ │ └── Infrastructure │ │ └── .gitkeep │ └── Shared │ ├── Domain │ ├── Criteria │ │ ├── CriteriaMother.php │ │ ├── FilterFieldMother.php │ │ ├── FilterMother.php │ │ ├── FilterValueMother.php │ │ ├── FiltersMother.php │ │ ├── OrderByMother.php │ │ └── OrderMother.php │ ├── DuplicatorMother.php │ ├── IntegerMother.php │ ├── MotherCreator.php │ ├── RandomElementPicker.php │ ├── Repeater.php │ ├── TestUtils.php │ ├── UuidMother.php │ └── WordMother.php │ ├── Infrastructure │ ├── ArchitectureTest.php │ ├── Arranger │ │ └── EnvironmentArranger.php │ ├── Behat │ │ ├── ApiContext.php │ │ └── ApplicationFeatureContext.php │ ├── Bus │ │ ├── Command │ │ │ ├── FakeCommand.php │ │ │ └── InMemorySymfonyCommandBusTest.php │ │ ├── Event │ │ │ ├── MySql │ │ │ │ └── MySqlDoctrineEventBusTest.php │ │ │ └── RabbitMq │ │ │ │ ├── RabbitMqEventBusTest.php │ │ │ │ └── TestAllWorksOnRabbitMqEventsPublished.php │ │ └── Query │ │ │ ├── FakeQuery.php │ │ │ ├── FakeResponse.php │ │ │ └── InMemorySymfonyQueryBusTest.php │ ├── ConstantRandomNumberGenerator.php │ ├── Doctrine │ │ └── MySqlDatabaseCleaner.php │ ├── Elastic │ │ └── ElasticDatabaseCleaner.php │ ├── Mink │ │ ├── MinkHelper.php │ │ └── MinkSessionRequestHelper.php │ ├── Mockery │ │ └── CodelyTvMatcherIsSimilar.php │ └── PhpUnit │ │ ├── Comparator │ │ ├── AggregateRootArraySimilarComparator.php │ │ ├── AggregateRootSimilarComparator.php │ │ ├── DateTimeSimilarComparator.php │ │ ├── DateTimeStringSimilarComparator.php │ │ ├── DomainEventArraySimilarComparator.php │ │ └── DomainEventSimilarComparator.php │ │ ├── Constraint │ │ └── CodelyTvConstraintIsSimilar.php │ │ ├── InfrastructureTestCase.php │ │ └── UnitTestCase.php │ └── SharedArchitectureTest.php ├── LICENSE └── README.md /02-publish_messages/1-add_rabbitmq/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | shared_rabbitmq: 5 | container_name: codely-rabbitmq_course-rabbitmq 6 | image: 'rabbitmq:3.12-management' 7 | restart: unless-stopped 8 | ports: 9 | - "5672:5672" 10 | - "15672:15672" 11 | environment: 12 | - RABBITMQ_DEFAULT_USER=codely 13 | - RABBITMQ_DEFAULT_PASS=codely 14 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/02-publish_messages/2-publish_event_to_rabbitmq/Makefile -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/seller_backoffice/product-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/seller_backoffice/products/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Very good product 2", 6 | "price": { 7 | "amount": 12, 8 | "currency": "EUR" 9 | }, 10 | "imageUrls": [ 11 | "https://picsum.photos/200/300" 12 | ], 13 | "creationDate": 1694472992 14 | } 15 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export interface EmailSender { 4 | send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/user/application/create/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/user/application/create/.gitkeep -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export interface RetentionUserRepository { 5 | save(user: RetentionUser): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | save(product: Product): Promise; 6 | 7 | search(id: ProductId): Promise; 8 | 9 | searchAll(): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductViews.ts: -------------------------------------------------------------------------------- 1 | export class ProductViews { 2 | constructor(public readonly value: number) {} 3 | 4 | static initialice(): ProductViews { 5 | return new ProductViews(0); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/AggregateRoot.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./event/DomainEvent"; 2 | 3 | export abstract class AggregateRoot { 4 | private domainEvents: DomainEvent[] = []; 5 | 6 | pullDomainEvents(): DomainEvent[] { 7 | const domainEvents = this.domainEvents; 8 | this.domainEvents = []; 9 | 10 | return domainEvents; 11 | } 12 | 13 | record(event: DomainEvent): void { 14 | this.domainEvents.push(event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface UuidGenerator { 2 | generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export interface EventBus { 4 | publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_rabbitmq/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/02-publish_messages/3-publication_fallback/Makefile -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/seller_backoffice/product-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/seller_backoffice/products/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Very good product 2", 6 | "price": { 7 | "amount": 12, 8 | "currency": "EUR" 9 | }, 10 | "imageUrls": [ 11 | "https://picsum.photos/200/300" 12 | ], 13 | "creationDate": 1694472992 14 | } 15 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export interface EmailSender { 4 | send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/retention/user/application/create/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/02-publish_messages/3-publication_fallback/src/contexts/retention/user/application/create/.gitkeep -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export interface RetentionUserRepository { 5 | save(user: RetentionUser): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | save(product: Product): Promise; 6 | 7 | search(id: ProductId): Promise; 8 | 9 | searchAll(): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/seller_backoffice/products/domain/ProductViews.ts: -------------------------------------------------------------------------------- 1 | export class ProductViews { 2 | constructor(public readonly value: number) {} 3 | 4 | static initialice(): ProductViews { 5 | return new ProductViews(0); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/AggregateRoot.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./event/DomainEvent"; 2 | 3 | export abstract class AggregateRoot { 4 | private domainEvents: DomainEvent[] = []; 5 | 6 | pullDomainEvents(): DomainEvent[] { 7 | const domainEvents = this.domainEvents; 8 | this.domainEvents = []; 9 | 10 | return domainEvents; 11 | } 12 | 13 | record(event: DomainEvent): void { 14 | this.domainEvents.push(event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface UuidGenerator { 2 | generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export interface EventBus { 4 | publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /02-publish_messages/3-publication_fallback/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/04-generate_queues_automatically/1-create_queues_in_rabbitmq/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/seller_backoffice/product-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/seller_backoffice/products/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Very good product 2", 6 | "price": { 7 | "amount": 12, 8 | "currency": "EUR" 9 | }, 10 | "imageUrls": [ 11 | "https://picsum.photos/200/300" 12 | ], 13 | "creationDate": 1694472992 14 | } 15 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export interface EmailSender { 4 | send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/user/application/create/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/user/application/create/.gitkeep -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export interface RetentionUserRepository { 5 | save(user: RetentionUser): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | save(product: Product): Promise; 6 | 7 | search(id: ProductId): Promise; 8 | 9 | searchAll(): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/seller_backoffice/products/domain/ProductViews.ts: -------------------------------------------------------------------------------- 1 | export class ProductViews { 2 | constructor(public readonly value: number) {} 3 | 4 | static initialice(): ProductViews { 5 | return new ProductViews(0); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/AggregateRoot.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./event/DomainEvent"; 2 | 3 | export abstract class AggregateRoot { 4 | private domainEvents: DomainEvent[] = []; 5 | 6 | pullDomainEvents(): DomainEvent[] { 7 | const domainEvents = this.domainEvents; 8 | this.domainEvents = []; 9 | 10 | return domainEvents; 11 | } 12 | 13 | record(event: DomainEvent): void { 14 | this.domainEvents.push(event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface UuidGenerator { 2 | generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export interface EventBus { 4 | publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-create_queues_in_rabbitmq/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export abstract class EmailSender { 4 | abstract send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/retention/email/infrastructure/FakeEmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | 3 | import { Email } from "../domain/Email"; 4 | import { EmailSender } from "../domain/EmailSender"; 5 | 6 | @Service() 7 | export class FakeEmailSender extends EmailSender { 8 | async send(_email: T): Promise { 9 | return Promise.resolve(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export abstract class RetentionUserRepository { 5 | abstract save(user: RetentionUser): Promise; 6 | 7 | abstract search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 8 | 9 | name(): string; 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export abstract class EventBus { 4 | abstract publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/infrastructure/OfficialUuidGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | import { UuidGenerator } from "../domain/UuidGenerator"; 5 | 6 | @Service() 7 | export class OfficialUuidGenerator extends UuidGenerator { 8 | async generate(): Promise { 9 | return uuidv4(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shared/infrastructure/dependency_injection/Subscriber.ts: -------------------------------------------------------------------------------- 1 | export const Subscriber = (queueName: string): ClassDecorator => { 2 | return (target: TFunction): TFunction => { 3 | return target; 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-create_queues_dynamically_in_rabbitmq/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/04-generate_queues_automatically/4-queues_with_wildcard_binding/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export abstract class EmailSender { 4 | abstract send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/retention/email/infrastructure/FakeEmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | 3 | import { Email } from "../domain/Email"; 4 | import { EmailSender } from "../domain/EmailSender"; 5 | 6 | @Service() 7 | export class FakeEmailSender extends EmailSender { 8 | async send(_email: T): Promise { 9 | return Promise.resolve(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export abstract class RetentionUserRepository { 5 | abstract save(user: RetentionUser): Promise; 6 | 7 | abstract search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 8 | 9 | name(): string; 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export abstract class EventBus { 4 | abstract publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/infrastructure/OfficialUuidGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | import { UuidGenerator } from "../domain/UuidGenerator"; 5 | 6 | @Service() 7 | export class OfficialUuidGenerator extends UuidGenerator { 8 | async generate(): Promise { 9 | return uuidv4(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shared/infrastructure/dependency_injection/Subscriber.ts: -------------------------------------------------------------------------------- 1 | export const Subscriber = (queueName: string): ClassDecorator => { 2 | return (target: TFunction): TFunction => { 3 | return target; 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/4-queues_with_wildcard_binding/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/05-add_resilience/1-consume_events/Makefile -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export abstract class EmailSender { 4 | abstract send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export abstract class RetentionUserRepository { 5 | abstract save(user: RetentionUser): Promise; 6 | 7 | abstract search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/AggregateRoot.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./event/DomainEvent"; 2 | 3 | export abstract class AggregateRoot { 4 | private domainEvents: DomainEvent[] = []; 5 | 6 | pullDomainEvents(): DomainEvent[] { 7 | const domainEvents = this.domainEvents; 8 | this.domainEvents = []; 9 | 10 | return domainEvents; 11 | } 12 | 13 | record(event: DomainEvent): void { 14 | this.domainEvents.push(event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/event/DomainEventClass.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventClass = { 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | new (...args: any[]): T; 6 | eventName: string; 7 | }; 8 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventClass } from "./DomainEventClass"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventClass[]; 8 | 9 | name(): string; 10 | } 11 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export abstract class EventBus { 4 | abstract publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/infrastructure/OfficialUuidGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | import { UuidGenerator } from "../domain/UuidGenerator"; 5 | 6 | @Service() 7 | export class OfficialUuidGenerator extends UuidGenerator { 8 | async generate(): Promise { 9 | return uuidv4(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shared/infrastructure/dependency_injection/Subscriber.ts: -------------------------------------------------------------------------------- 1 | export const Subscriber = (queueName: string): ClassDecorator => { 2 | return (target: TFunction): TFunction => { 3 | return target; 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/05-add_resilience/3-implement_retry_dead_letter/Makefile -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/product_review-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/product_reviews/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "userId": "0a1f3c6d-96e0-48e8-b1a5-7fe90b542962", 6 | "productId": "34878c68-dd03-438d-9f4a-da6e53cf8ca4", 7 | "rating": 4, 8 | "comment": "Bien bien" 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/shop/user-PUT.http: -------------------------------------------------------------------------------- 1 | PUT http://localhost:3000/api/shop/users/{{$random.uuid}} 2 | Content-Type: application/json 3 | 4 | { 5 | "name": "Javier Ferrer", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | experimental: { 4 | serverActions: true, 5 | }, 6 | images: { 7 | remotePatterns: [ 8 | { 9 | protocol: "https", 10 | hostname: "picsum.photos", 11 | port: "", 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/retention/email/domain/EmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Email } from "./Email"; 2 | 3 | export abstract class EmailSender { 4 | abstract send(email: T): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/retention/email/infrastructure/FakeEmailSender.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | 3 | import { Email } from "../domain/Email"; 4 | import { EmailSender } from "../domain/EmailSender"; 5 | 6 | @Service() 7 | export class FakeEmailSender extends EmailSender { 8 | async send(_email: T): Promise { 9 | // eslint-disable-next-line no-console 10 | throw new Error("Email service is down"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/retention/user/domain/RetentionUserRepository.ts: -------------------------------------------------------------------------------- 1 | import { UserId } from "../../../shop/users/domain/UserId"; 2 | import { RetentionUser } from "./RetentionUser"; 3 | 4 | export abstract class RetentionUserRepository { 5 | abstract save(user: RetentionUser): Promise; 6 | 7 | abstract search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/AggregateRoot.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./event/DomainEvent"; 2 | 3 | export abstract class AggregateRoot { 4 | private domainEvents: DomainEvent[] = []; 5 | 6 | pullDomainEvents(): DomainEvent[] { 7 | const domainEvents = this.domainEvents; 8 | this.domainEvents = []; 9 | 10 | return domainEvents; 11 | } 12 | 13 | record(event: DomainEvent): void { 14 | this.domainEvents.push(event); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/Money.ts: -------------------------------------------------------------------------------- 1 | export type Currency = "EUR" | "USD"; 2 | 3 | export type Money = { 4 | amount: number; 5 | currency: Currency; 6 | }; 7 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/event/DomainEventClass.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventClass = { 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | new (...args: any[]): T; 6 | eventName: string; 7 | }; 8 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/event/DomainEventSubscriber.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | import { DomainEventClass } from "./DomainEventClass"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventClass[]; 8 | 9 | name(): string; 10 | } 11 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/domain/event/EventBus.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export abstract class EventBus { 4 | abstract publish(events: DomainEvent[]): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/infrastructure/OfficialUuidGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Service } from "diod"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | 4 | import { UuidGenerator } from "../domain/UuidGenerator"; 5 | 6 | @Service() 7 | export class OfficialUuidGenerator extends UuidGenerator { 8 | async generate(): Promise { 9 | return uuidv4(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shared/infrastructure/dependency_injection/Subscriber.ts: -------------------------------------------------------------------------------- 1 | export const Subscriber = (queueName: string): ClassDecorator => { 2 | return (target: TFunction): TFunction => { 3 | return target; 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/product_reviews/domain/ProductReviewRepository.ts: -------------------------------------------------------------------------------- 1 | import { ProductId } from "../../products/domain/ProductId"; 2 | import { ProductReview } from "./ProductReview"; 3 | 4 | export interface ProductReviewRepository { 5 | save(review: ProductReview): Promise; 6 | 7 | searchByProduct(productId: ProductId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/application/search_all/AllProductsSearcher.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "../../domain/Product"; 2 | import { ProductRepository } from "../../domain/ProductRepository"; 3 | 4 | export class AllProductsSearcher { 5 | constructor(private readonly repository: ProductRepository) {} 6 | 7 | async search(): Promise { 8 | return this.repository.searchAll(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/products/domain/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./Product"; 2 | import { ProductId } from "./ProductId"; 3 | 4 | export interface ProductRepository { 5 | search(id: ProductId): Promise; 6 | 7 | searchAll(): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/application/search/UserSearcher.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../../domain/User"; 2 | import { UserId } from "../../domain/UserId"; 3 | import { UserRepository } from "../../domain/UserRepository"; 4 | 5 | export class UserSearcher { 6 | constructor(private readonly repository: UserRepository) {} 7 | 8 | async search(id: string): Promise { 9 | return this.repository.search(new UserId(id)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserDoesNotExist.ts: -------------------------------------------------------------------------------- 1 | export class UserDoesNotExist extends Error { 2 | constructor(id: string) { 3 | super(`The user ${id} does not exist`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./User"; 2 | import { UserId } from "./UserId"; 3 | 4 | export interface UserRepository { 5 | save(user: User): Promise; 6 | 7 | search(id: UserId): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/retention/email/domain/EmailBodyMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailBody } from "../../../../../src/contexts/retention/email/domain/EmailBody"; 4 | 5 | export class EmailBodyMother { 6 | static create(value?: string): EmailBody { 7 | return new EmailBody(value ?? faker.string.alpha()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/retention/email/domain/EmailIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailId } from "../../../../../src/contexts/retention/email/domain/EmailId"; 4 | 5 | export class EmailIdMother { 6 | static create(value?: string): EmailId { 7 | return new EmailId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/shared/domain/EmailAddressMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { EmailAddress } from "../../../../src/contexts/shared/domain/EmailAddress"; 4 | 5 | export class EmailAddressMother { 6 | static create(value?: string): EmailAddress { 7 | return new EmailAddress(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/shop/users/domain/UserEmailMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserEmail } from "../../../../../src/contexts/shop/users/domain/UserEmail"; 4 | 5 | export class UserEmailMother { 6 | static create(value?: string): UserEmail { 7 | return new UserEmail(value ?? faker.internet.email()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/shop/users/domain/UserIdMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserId } from "../../../../../src/contexts/shop/users/domain/UserId"; 4 | 5 | export class UserIdMother { 6 | static create(value?: string): UserId { 7 | return new UserId(value ?? faker.string.uuid()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/shop/users/domain/UserNameMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserName } from "../../../../../src/contexts/shop/users/domain/UserName"; 4 | 5 | export class UserNameMother { 6 | static create(value?: string): UserName { 7 | return new UserName(value ?? faker.person.firstName()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/tests/contexts/shop/users/domain/UserProfilePictureMother.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | 3 | import { UserProfilePicture } from "../../../../../src/contexts/shop/users/domain/UserProfilePicture"; 4 | 5 | export class UserProfilePictureMother { 6 | static create(value?: string): UserProfilePicture { 7 | return new UserProfilePicture(value ?? faker.image.url()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://bit.ly/CodelyTvPro 2 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/.gitignore: -------------------------------------------------------------------------------- 1 | /.env.local 2 | /.env.*.local 3 | 4 | /apps/*/*/var/ 5 | !/apps/*/*/var/.gitkeep 6 | 7 | /apps/*/*/build/ 8 | !/apps/*/*/build/supervisor/.gitkeep 9 | 10 | /vendor/ 11 | .phpunit.result.cache 12 | 13 | /build 14 | 15 | .php-cs-fixer.cache 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/backend/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 7 | FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], 8 | // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] 9 | ]; 10 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/backend/config/routes/courses.yaml: -------------------------------------------------------------------------------- 1 | courses_get: 2 | path: /courses 3 | controller: CodelyTv\Apps\Backoffice\Backend\Controller\Courses\CoursesGetController 4 | defaults: { auth: false } 5 | methods: [GET] 6 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/backend/config/routes/health-check.yaml: -------------------------------------------------------------------------------- 1 | health-check_get: 2 | path: /health-check 3 | controller: CodelyTv\Apps\Backoffice\Backend\Controller\HealthCheck\HealthCheckGetController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/backend/config/routes/metrics.yaml: -------------------------------------------------------------------------------- 1 | metrics_get: 2 | path: /metrics 3 | controller: CodelyTv\Apps\Backoffice\Backend\Controller\Metrics\MetricsController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/backend/config/services_test.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | 4 | services: 5 | _defaults: 6 | autoconfigure: true 7 | autowire: true 8 | 9 | CodelyTv\Tests\: 10 | resource: '../../../../tests' 11 | 12 | # -- IMPLEMENTATIONS SELECTOR -- 13 | CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus' 14 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/routes/api_courses.yaml: -------------------------------------------------------------------------------- 1 | api_courses_get: 2 | path: /api/courses 3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\ApiCoursesGetController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/routes/courses.yaml: -------------------------------------------------------------------------------- 1 | courses_get: 2 | path: /courses 3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesGetWebController 4 | methods: [GET] 5 | 6 | courses_post: 7 | path: /courses 8 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Courses\CoursesPostWebController 9 | methods: [POST] 10 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/routes/health-check.yaml: -------------------------------------------------------------------------------- 1 | health-check_get: 2 | path: /health-check 3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\HealthCheck\HealthCheckGetController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/routes/home.yaml: -------------------------------------------------------------------------------- 1 | home_get: 2 | path: / 3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Home\HomeGetWebController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/routes/metrics.yaml: -------------------------------------------------------------------------------- 1 | metrics_get: 2 | path: /metrics 3 | controller: CodelyTv\Apps\Backoffice\Frontend\Controller\Metrics\MetricsController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/config/services_test.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | 4 | services: 5 | _defaults: 6 | autoconfigure: true 7 | autowire: true 8 | 9 | CodelyTv\Tests\: 10 | resource: '../../../../tests' 11 | 12 | # -- IMPLEMENTATIONS SELECTOR -- 13 | CodelyTv\Shared\Domain\Bus\Event\EventBus: '@CodelyTv\Shared\Infrastructure\Bus\Event\InMemory\InMemorySymfonyEventBus' 14 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/public/images/logo.png -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/templates/pages/home.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'master.html.twig' %} 2 | 3 | {% block page_title %}HOME{% endblock %} 4 | 5 | {% block main %} 6 | HOLIII HOME 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/backoffice/frontend/templates/partials/footer.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 🤙 CodelyTV - El mejor backoffice de la historia 5 |

6 |
7 |
8 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 7 | FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle::class => ['test' => true], 8 | // WouterJ\EloquentBundle\WouterJEloquentBundle::class => ['test' => true] 9 | ]; 10 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/config/routes/courses.yaml: -------------------------------------------------------------------------------- 1 | courses_put: 2 | path: /courses/{id} 3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\Courses\CoursesPutController 4 | methods: [PUT] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/config/routes/courses_counter.yaml: -------------------------------------------------------------------------------- 1 | courses_counter_get: 2 | path: /courses-counter 3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\CoursesCounter\CoursesCounterGetController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/config/routes/health-check.yaml: -------------------------------------------------------------------------------- 1 | health-check_get: 2 | path: /health-check 3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\HealthCheck\HealthCheckGetController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/config/routes/metrics.yaml: -------------------------------------------------------------------------------- 1 | metrics_get: 2 | path: /metrics 3 | controller: CodelyTv\Apps\Mooc\Backend\Controller\Metrics\MetricsController 4 | methods: [GET] 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/backend/tests/features/health_check/health_check_get.feature: -------------------------------------------------------------------------------- 1 | Feature: Api status 2 | In order to know the server is up and running 3 | As a health check 4 | I want to check the api status 5 | 6 | Scenario: Check the api status 7 | Given I send a GET request to "/health-check" 8 | Then the response content should be: 9 | """ 10 | { 11 | "mooc-backend": "ok", 12 | "rand": 1 13 | } 14 | """ 15 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/frontend/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-rabbitmq-course/f4e79faa8e19843918510cff583652e7623afeff/06-rabbitmq_particularities/1-dynamic_consumers_in_php/apps/mooc/frontend/src/.gitkeep -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/behat.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - apps/mooc/backend/tests/mooc_backend.yml 3 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/databases/backoffice/courses.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": { 3 | "courses": { 4 | "properties": { 5 | "id": { 6 | "type": "keyword", 7 | "index": true 8 | }, 9 | "name": { 10 | "type": "text", 11 | "index": true 12 | }, 13 | "duration": { 14 | "type": "text", 15 | "index": true 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/endpoints/backoffice_frontend.http: -------------------------------------------------------------------------------- 1 | # ELASTIC - Search 2 | POST localhost:9200/backoffice_courses/_search 3 | Content-Type: application/json 4 | 5 | { 6 | "query": { 7 | "term": { 8 | "name": "Pepe" 9 | } 10 | } 11 | } 12 | 13 | ### 14 | # ELASTIC - Search 15 | POST localhost:9200/backoffice_courses/_search 16 | Content-Type: application/json 17 | 18 | ### 19 | 20 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/endpoints/mooc_backend.http: -------------------------------------------------------------------------------- 1 | # Create a course 2 | PUT localhost:8030/courses/{{$uuid}} 3 | Content-Type: application/json 4 | 5 | { 6 | "name": "The best course", 7 | "duration": "5 hours" 8 | } 9 | 10 | ### 11 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/infrastructure/php/conf.d/apcu.ini: -------------------------------------------------------------------------------- 1 | apc.enable_cli=1 2 | apc.enabled=1 3 | apc.shm_segments=1 4 | apc.shm_size=256M 5 | apc.num_files_hint=7000 6 | apc.user_entries_hint=4096 7 | apc.ttl=7200 8 | apc.user_ttl=7200 9 | apc.gc_ttl=3600 10 | apc.max_file_size=1M 11 | apc.stat=1 12 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/infrastructure/php/conf.d/opcache.ini: -------------------------------------------------------------------------------- 1 | opcache.memory_consumption=128 2 | opcache.interned_strings_buffer=8 3 | opcache.max_accelerated_files=4000 4 | opcache.revalidate_freq=60 5 | opcache.fast_shutdown=1 6 | opcache.enable_cli=1 7 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/infrastructure/php/conf.d/xdebug.ini: -------------------------------------------------------------------------------- 1 | zend_extension = xdebug.so 2 | 3 | ;Debugging 4 | xdebug.mode=debug 5 | xdebug.start_with_request = yes 6 | xdebug.discover_client_host = yes 7 | xdebug.client_port = 9001 8 | xdebug.client_host = host.docker.internal 9 | 10 | ;Profiling 11 | xdebug.mode=profile 12 | xdebug.start_with_request=trigger 13 | 14 | xdebug.output_dir = "/tmp/xdebug" 15 | xdebug.max_nesting_level = 500 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/etc/infrastructure/php/php.ini: -------------------------------------------------------------------------------- 1 | date.timezone = "UTC" 2 | html_errors = "On" 3 | display_errors = "On" 4 | error_reporting = E_ALL 5 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventAggregateId.php: -------------------------------------------------------------------------------- 1 | value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Analytics/DomainEvents/Domain/AnalyticsDomainEventId.php: -------------------------------------------------------------------------------- 1 | value() === $other->value(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Backoffice/Auth/Domain/AuthRepository.php: -------------------------------------------------------------------------------- 1 | are invalid', $username->value())); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Backoffice/Auth/Domain/InvalidAuthUsername.php: -------------------------------------------------------------------------------- 1 | does not exists', $username->value())); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Backoffice/Courses/Application/SearchAll/SearchAllBackofficeCoursesQuery.php: -------------------------------------------------------------------------------- 1 | id; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Mooc/Videos/Application/Trim/VideoTrimmer.php: -------------------------------------------------------------------------------- 1 | value === self::NONE->value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Shared/Domain/DomainError.php: -------------------------------------------------------------------------------- 1 | errorMessage()); 14 | } 15 | 16 | abstract public function errorCode(): string; 17 | 18 | abstract protected function errorMessage(): string; 19 | } 20 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Shared/Domain/Logger.php: -------------------------------------------------------------------------------- 1 | value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php: -------------------------------------------------------------------------------- 1 | toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/tests/Mooc/Courses/Domain/CourseIdMother.php: -------------------------------------------------------------------------------- 1 | randomElement($elements); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/tests/Shared/Domain/UuidMother.php: -------------------------------------------------------------------------------- 1 | unique()->uuid; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/tests/Shared/Domain/WordMother.php: -------------------------------------------------------------------------------- 1 | word; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/tests/Shared/Infrastructure/Arranger/EnvironmentArranger.php: -------------------------------------------------------------------------------- 1 | number; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /06-rabbitmq_particularities/1-dynamic_consumers_in_php/tests/Shared/Infrastructure/ConstantRandomNumberGenerator.php: -------------------------------------------------------------------------------- 1 |