├── 02-publish_messages ├── 1-prepare_localstack │ ├── .gitignore │ ├── 1-configure.sh │ ├── 2-list_queues.sh │ ├── 3-publish_message.sh │ ├── 4-consume_message.sh │ ├── 5-delete_message.sh │ └── docker-compose.yml ├── 2-publish_event_to_eventbridge │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-configure.sh │ ├── 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 │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ └── InMemoryEventBus.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 │ ├── 1-configure.sh │ ├── 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 │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ └── failover │ │ │ └── DomainEventFailover.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-configure_programatically │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-configure.sh │ ├── 2-test_message.sh │ ├── 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-eventbridge_sqs.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 │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── failover │ │ │ │ └── DomainEventFailover.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-generate_queues_dynamically │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.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 │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── failover │ │ │ │ └── DomainEventFailover.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-queues_with_wildcard_binding │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.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 │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ └── failover │ │ │ └── DomainEventFailover.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 │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.ts │ │ │ │ └── consume-sqs.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 │ │ │ │ │ ├── DomainEventClass.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ ├── OfficialUuidGenerator.ts │ │ │ │ ├── dependency_injection │ │ │ │ ├── Subscriber.ts │ │ │ │ └── diod.config.ts │ │ │ │ └── event_bus │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── failover │ │ │ │ └── DomainEventFailover.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 │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.ts │ │ │ └── consume-sqs.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 │ │ │ │ ├── DomainEventClass.ts │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ └── EventBus.ts │ │ └── infrastructure │ │ │ ├── MariaDBConnection.ts │ │ │ ├── OfficialUuidGenerator.ts │ │ │ ├── dependency_injection │ │ │ ├── Subscriber.ts │ │ │ └── diod.config.ts │ │ │ └── event_bus │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ └── failover │ │ │ └── DomainEventFailover.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-to_production ├── 1-from_localstack_to_aws │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.ts │ │ │ │ └── consume-sqs.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 │ │ │ │ │ ├── DomainEventClass.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ ├── OfficialUuidGenerator.ts │ │ │ │ ├── dependency_injection │ │ │ │ ├── Subscriber.ts │ │ │ │ └── diod.config.ts │ │ │ │ └── event_bus │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── failover │ │ │ │ └── DomainEventFailover.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 ├── 2-generate_terraform_files │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.ts │ │ │ │ ├── consume-sqs.ts │ │ │ │ └── generate-eventbridge_sqs-terraform_files.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 │ │ │ │ │ ├── DomainEventClass.ts │ │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ │ └── EventBus.ts │ │ │ └── infrastructure │ │ │ │ ├── MariaDBConnection.ts │ │ │ │ ├── OfficialUuidGenerator.ts │ │ │ │ ├── dependency_injection │ │ │ │ ├── Subscriber.ts │ │ │ │ └── diod.config.ts │ │ │ │ └── event_bus │ │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ │ ├── InMemoryEventBus.ts │ │ │ │ └── failover │ │ │ │ └── DomainEventFailover.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-schema_registry │ ├── .editorconfig │ ├── .eslintrc.json │ ├── .gitignore │ ├── 1-test_messages.sh │ ├── 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-eventbridge_sqs.ts │ │ │ ├── consume-sqs.ts │ │ │ └── generate-eventbridge_sqs-terraform_files.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 │ │ │ │ ├── DomainEventClass.ts │ │ │ │ ├── DomainEventSubscriber.ts │ │ │ │ └── EventBus.ts │ │ └── infrastructure │ │ │ ├── MariaDBConnection.ts │ │ │ ├── OfficialUuidGenerator.ts │ │ │ ├── dependency_injection │ │ │ ├── Subscriber.ts │ │ │ └── diod.config.ts │ │ │ └── event_bus │ │ │ ├── AwsEventBridgeEventBus.ts │ │ │ ├── DomainEventJsonDeserializer.ts │ │ │ ├── DomainEventJsonSerializer.ts │ │ │ ├── InMemoryEventBus.ts │ │ │ └── failover │ │ │ └── DomainEventFailover.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 ├── LICENSE └── README.md /02-publish_messages/1-prepare_localstack/.gitignore: -------------------------------------------------------------------------------- 1 | /volume 2 | -------------------------------------------------------------------------------- /02-publish_messages/1-prepare_localstack/2-list_queues.sh: -------------------------------------------------------------------------------- 1 | aws sqs --endpoint-url http://localhost:4566 list-queues | jq '.QueueUrls' 2 | -------------------------------------------------------------------------------- /02-publish_messages/1-prepare_localstack/3-publish_message.sh: -------------------------------------------------------------------------------- 1 | aws events put-events \ 2 | --endpoint-url http://localhost:4566 \ 3 | --region us-east-1 \ 4 | --entries '[{ 5 | "EventBusName": "codely.domain_events", 6 | "Source": "codely", 7 | "DetailType": "user.registered", 8 | "Detail": "{ \"user_id\": \"123\", \"email\": \"javi@hola.com\" }" 9 | }]' 10 | -------------------------------------------------------------------------------- /02-publish_messages/1-prepare_localstack/4-consume_message.sh: -------------------------------------------------------------------------------- 1 | aws sqs receive-message \ 2 | --endpoint-url http://localhost:4566 \ 3 | --region us-east-1 \ 4 | --queue-url http://localhost:4566/000000000000/send_welcome_email_on_user_registered 5 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/02-publish_messages/2-publish_event_to_eventbridge/Makefile -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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 Patatas 2", 6 | "email": "javierferrer@gmail.com", 7 | "profilePicture": "https://" 8 | } 9 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/src/contexts/retention/user/application/create/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/02-publish_messages/2-publish_event_to_eventbridge/src/contexts/retention/user/application/create/.gitkeep -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface UuidGenerator { 2 | generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /02-publish_messages/2-publish_event_to_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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_eventbridge/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-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/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-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/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/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/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-configure_programatically/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/2-test_message.sh: -------------------------------------------------------------------------------- 1 | aws events put-events \ 2 | --endpoint-url http://localhost:4566 \ 3 | --region us-east-1 \ 4 | --entries '[{ 5 | "EventBusName": "codely.domain_events", 6 | "Source": "codely", 7 | "DetailType": "codely.shop.user.registered", 8 | "Detail": "{ \"user_id\": \"123\", \"email\": \"javi@hola.com\" }" 9 | }]' 10 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/04-generate_queues_automatically/1-configure_programatically/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/src/contexts/retention/user/application/create/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/04-generate_queues_automatically/1-configure_programatically/src/contexts/retention/user/application/create/.gitkeep -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export interface UuidGenerator { 2 | generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/1-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-configure_programatically/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-generate_queues_dynamically/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/04-generate_queues_automatically/2-generate_queues_dynamically/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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/2-generate_queues_dynamically/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/2-generate_queues_dynamically/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/src/contexts/shared/domain/event/DomainEventName.ts: -------------------------------------------------------------------------------- 1 | import { DomainEvent } from "./DomainEvent"; 2 | 3 | export type DomainEventName = Pick; 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/2-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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-generate_queues_dynamically/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/3-queues_with_wildcard_binding/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/3-queues_with_wildcard_binding/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/04-generate_queues_automatically/3-queues_with_wildcard_binding/Makefile -------------------------------------------------------------------------------- /04-generate_queues_automatically/3-queues_with_wildcard_binding/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/3-queues_with_wildcard_binding/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/3-queues_with_wildcard_binding/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/3-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/3-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/3-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/3-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/3-queues_with_wildcard_binding/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/3-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/3-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/3-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/3-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/3-queues_with_wildcard_binding/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /04-generate_queues_automatically/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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/3-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-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/05-add_resilience/1-consume_events/Makefile -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/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 | -------------------------------------------------------------------------------- /05-add_resilience/1-consume_events/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 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/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-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/05-add_resilience/3-implement_retry_dead_letter/Makefile -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/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 | -------------------------------------------------------------------------------- /05-add_resilience/3-implement_retry_dead_letter/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /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/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/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 { DomainEventName } from "./DomainEventName"; 3 | 4 | export interface DomainEventSubscriber { 5 | on(domainEvent: T): Promise; 6 | 7 | subscribedTo(): DomainEventName[]; 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/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-to_production/1-from_localstack_to_aws/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/06-to_production/1-from_localstack_to_aws/Makefile -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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 | -------------------------------------------------------------------------------- /06-to_production/1-from_localstack_to_aws/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-to_production/2-generate_terraform_files/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/06-to_production/2-generate_terraform_files/Makefile -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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 | -------------------------------------------------------------------------------- /06-to_production/2-generate_terraform_files/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-to_production/3-schema_registry/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.tsx] 2 | indent_style = tab 3 | tab_width = 2 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodelyTV/infrastructure_design-eventbus-aws-course/ff25e022f054f517bf84ba395337eaaa9c2742fd/06-to_production/3-schema_registry/Makefile -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/seller_backoffice/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products/8910e9e9-3567-4e1a-8ffa-9491bdbda12e 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/seller_backoffice/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/seller_backoffice/products 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/shared/event_bus-POST.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/api/shared/event_bus 2 | Content-Type: application/json 3 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/shop/product-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products/34878c68-dd03-438d-9f4a-da6e53cf8ca4 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/shop/product_reviews-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/product_reviews?product_id=1f2a73e3-e0e9-4418-8a53-d080871b24b3 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/shop/products-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/products 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/etc/http/shop/user-GET.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/shop/users/1ec0b8ee-dbdc-48f6-ae5a-bfb99bee5e27 2 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | }; 6 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/retention/email/domain/EmailBody.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class EmailBody extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/retention/email/domain/EmailId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class EmailId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | console.log("Email sent!"); 10 | 11 | return Promise.resolve(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shared/domain/Collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class Collection { 2 | constructor(public readonly value: T[]) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shared/domain/EmailAddress.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export class EmailAddress extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shared/domain/Identifier.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "./StringValueObject"; 2 | 3 | export abstract class Identifier extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shared/domain/StringValueObject.ts: -------------------------------------------------------------------------------- 1 | export abstract class StringValueObject { 2 | constructor(public readonly value: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shared/domain/UuidGenerator.ts: -------------------------------------------------------------------------------- 1 | export abstract class UuidGenerator { 2 | abstract generate(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/product_reviews/domain/ProductReviewComment.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductReviewComment extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/product_reviews/domain/ProductReviewId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductReviewId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/product_reviews/domain/ProductReviewRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductReviewRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/products/domain/ProductId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class ProductId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/products/domain/ProductImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductImageUrl extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/products/domain/ProductName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class ProductName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/products/domain/ProductRating.ts: -------------------------------------------------------------------------------- 1 | export class ProductRating { 2 | constructor(public readonly value: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/users/domain/UserEmail.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserEmail extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/users/domain/UserId.ts: -------------------------------------------------------------------------------- 1 | import { Identifier } from "../../../shared/domain/Identifier"; 2 | 3 | export class UserId extends Identifier {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/users/domain/UserName.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserName extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/users/domain/UserProfilePicture.ts: -------------------------------------------------------------------------------- 1 | import { StringValueObject } from "../../../shared/domain/StringValueObject"; 2 | 3 | export class UserProfilePicture extends StringValueObject {} 4 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/src/contexts/shop/users/domain/UserStatus.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatus { 2 | Active = "active", 3 | Archived = "archived", 4 | } 5 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | -------------------------------------------------------------------------------- /06-to_production/3-schema_registry/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 | --------------------------------------------------------------------------------