├── .gitignore ├── README.md ├── clone-and-run-devexp-using-testcontainers ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── .sdkmanrc ├── LICENSE ├── README.md ├── app.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── demo │ │ │ ├── Application.java │ │ │ ├── api │ │ │ └── ProductController.java │ │ │ ├── domain │ │ │ ├── Product.java │ │ │ └── ProductRepository.java │ │ │ └── events │ │ │ └── ProductEventListener.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ └── V1__init.sql │ └── test │ ├── java │ └── com │ │ └── sivalabs │ │ └── demo │ │ ├── ContainersConfig.java │ │ ├── TestApplication.java │ │ ├── api │ │ └── ProductControllerTest.java │ │ └── events │ │ └── ProductEventListenerTest.java │ └── resources │ ├── application-test.properties │ └── logback-test.xml ├── spring-boot-actuator ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── myapp │ │ │ ├── Customer.java │ │ │ ├── CustomerController.java │ │ │ ├── CustomerNotFoundException.java │ │ │ ├── CustomerRepository.java │ │ │ ├── CustomerService.java │ │ │ ├── DataInitializer.java │ │ │ ├── MyScheduledJobs.java │ │ │ └── SpringBootActuatorApplication.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ ├── V1__SchemaInit.sql │ │ └── V2__Add_Users_Table.sql │ └── test │ └── java │ └── com │ └── sivalabs │ └── myapp │ └── SpringBootActuatorApplicationTests.java ├── spring-boot-admin-server ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── adminserver │ │ │ └── SpringBootAdminServerApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── sivalabs │ └── adminserver │ └── SpringBootAdminServerApplicationTests.java ├── spring-boot-docker-compose-demo ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── compose.yaml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── demo │ │ │ ├── Application.java │ │ │ ├── Bookmark.java │ │ │ ├── BookmarkController.java │ │ │ ├── BookmarkCreatedEvent.java │ │ │ ├── BookmarkEventHandler.java │ │ │ ├── BookmarkRepository.java │ │ │ └── CreateBookmarkRequest.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ └── V1__bookmarks_table.sql │ └── test │ ├── java │ └── com │ │ └── sivalabs │ │ └── demo │ │ └── BookmarkControllerTests.java │ └── resources │ └── test-data.sql ├── spring-boot-exception-handling ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── myapp │ │ │ ├── ExceptionHandling.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── HomeController.java │ │ │ ├── SpringBootExceptionHandlingApplication.java │ │ │ ├── domain │ │ │ ├── Customer.java │ │ │ ├── CustomerNotFoundException.java │ │ │ ├── CustomerRepository.java │ │ │ ├── CustomerService.java │ │ │ └── ErrorResponse.java │ │ │ └── rest │ │ │ ├── CustomerController.java │ │ │ └── OrderController.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ ├── demo.html │ │ ├── error.html │ │ └── error │ │ ├── 400.html │ │ └── 403.html │ └── test │ └── java │ └── com │ └── sivalabs │ └── myapp │ └── SpringBootExceptionHandlingApplicationTests.java ├── spring-boot-htmx-demo ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── compose.yaml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── bookstore │ │ │ ├── Application.java │ │ │ ├── ApplicationProperties.java │ │ │ ├── catalog │ │ │ ├── domain │ │ │ │ ├── PagedResult.java │ │ │ │ ├── Product.java │ │ │ │ ├── ProductEntity.java │ │ │ │ ├── ProductMapper.java │ │ │ │ ├── ProductNotFoundException.java │ │ │ │ ├── ProductRepository.java │ │ │ │ └── ProductService.java │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── ProductController.java │ │ │ └── orders │ │ │ ├── domain │ │ │ ├── InvalidOrderException.java │ │ │ ├── OrderCancellationFn.java │ │ │ ├── OrderEntity.java │ │ │ ├── OrderItemEntity.java │ │ │ ├── OrderMapper.java │ │ │ ├── OrderNotFoundException.java │ │ │ ├── OrderRepository.java │ │ │ ├── OrderService.java │ │ │ ├── OrderStatusEnquiryFn.java │ │ │ ├── SecurityService.java │ │ │ └── models │ │ │ │ ├── Address.java │ │ │ │ ├── Cart.java │ │ │ │ ├── CreateOrderRequest.java │ │ │ │ ├── CreateOrderResponse.java │ │ │ │ ├── Customer.java │ │ │ │ ├── OrderDTO.java │ │ │ │ ├── OrderForm.java │ │ │ │ ├── OrderItem.java │ │ │ │ ├── OrderStatus.java │ │ │ │ └── OrderSummary.java │ │ │ └── web │ │ │ └── controllers │ │ │ ├── CartController.java │ │ │ ├── ChatSupportController.java │ │ │ ├── ControllerAdvice.java │ │ │ └── OrderController.java │ └── resources │ │ ├── application.properties │ │ ├── db │ │ └── migration │ │ │ ├── V1__create_products_table.sql │ │ │ ├── V2__add_books_data.sql │ │ │ ├── V3__create_order_tables.sql │ │ │ └── V4__add_sample_orders.sql │ │ ├── static │ │ ├── css │ │ │ └── styles.css │ │ └── images │ │ │ └── books.png │ │ └── templates │ │ ├── cart.html │ │ ├── chat.html │ │ ├── layout.html │ │ ├── order_details.html │ │ ├── orders.html │ │ ├── partials │ │ ├── cart-item-count.html │ │ ├── cart.html │ │ ├── chat-answer.html │ │ ├── order-form.html │ │ ├── orders.html │ │ ├── pagination.html │ │ └── products.html │ │ └── products.html │ └── test │ └── java │ └── com │ └── sivalabs │ └── bookstore │ ├── AbstractIT.java │ ├── ApplicationTests.java │ ├── ContainersConfig.java │ └── TestApplication.java ├── spring-boot-logging ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── myapp │ │ │ ├── DemoController.java │ │ │ ├── MDCFilter.java │ │ │ └── SpringBootLoggingApplication.java │ └── resources │ │ ├── application.properties │ │ └── logback-spring.xml │ └── test │ └── java │ └── com │ └── sivalabs │ └── myapp │ └── SpringBootLoggingApplicationTests.java ├── spring-boot-missing-guide ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ ├── aop │ │ │ ├── Customer.java │ │ │ ├── CustomerService.java │ │ │ ├── CustomerServiceProxy.java │ │ │ ├── ICustomerService.java │ │ │ ├── Main.java │ │ │ ├── Person.java │ │ │ ├── PersonService.java │ │ │ ├── TransactionJdkProxy.java │ │ │ └── TransactionalCglibProxy.java │ │ │ ├── di │ │ │ ├── BackgroundVerificationService.java │ │ │ ├── BeanFactory.java │ │ │ ├── IBackgroundVerificationService.java │ │ │ ├── LoanRequest.java │ │ │ ├── LoanService.java │ │ │ └── MockBackgroundVerificationService.java │ │ │ ├── hello │ │ │ └── BookmarkNotFoundException.java │ │ │ ├── sbmg │ │ │ ├── HelloController.java │ │ │ └── SpringBootMissingGuideApplication.java │ │ │ └── templatee │ │ │ ├── Customer.java │ │ │ ├── JdbcDemo.java │ │ │ └── JdbcTemplateDemo.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── sivalabs │ ├── di │ ├── LoanServiceTest.java │ ├── Main.java │ └── SpringMain.java │ └── sbmg │ └── SpringBootMissingGuideApplicationTests.java ├── spring-boot-modulith-demo ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── modulith │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── sivalabs │ │ │ │ └── bookstore │ │ │ │ ├── Application.java │ │ │ │ ├── ApplicationProperties.java │ │ │ │ ├── catalog │ │ │ │ ├── Product.java │ │ │ │ ├── ProductService.java │ │ │ │ ├── config │ │ │ │ │ └── CatalogExceptionHandler.java │ │ │ │ ├── domain │ │ │ │ │ ├── ProductEntity.java │ │ │ │ │ ├── ProductNotFoundException.java │ │ │ │ │ ├── ProductRepository.java │ │ │ │ │ └── ProductServiceImpl.java │ │ │ │ └── web │ │ │ │ │ └── controllers │ │ │ │ │ └── ProductController.java │ │ │ │ ├── common │ │ │ │ ├── models │ │ │ │ │ └── PagedResult.java │ │ │ │ └── package-info.java │ │ │ │ ├── inventory │ │ │ │ └── InventoryService.java │ │ │ │ └── orders │ │ │ │ ├── config │ │ │ │ └── OrdersExceptionHandler.java │ │ │ │ ├── domain │ │ │ │ ├── InvalidOrderException.java │ │ │ │ ├── OrderEntity.java │ │ │ │ ├── OrderMapper.java │ │ │ │ ├── OrderNotFoundException.java │ │ │ │ ├── OrderRepository.java │ │ │ │ ├── OrderService.java │ │ │ │ ├── events │ │ │ │ │ ├── OrderCreatedEvent.java │ │ │ │ │ └── package-info.java │ │ │ │ └── models │ │ │ │ │ ├── CreateOrderRequest.java │ │ │ │ │ ├── CreateOrderResponse.java │ │ │ │ │ ├── Customer.java │ │ │ │ │ ├── OrderDTO.java │ │ │ │ │ ├── OrderItem.java │ │ │ │ │ ├── OrderStatus.java │ │ │ │ │ └── OrderSummary.java │ │ │ │ ├── package-info.java │ │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── OrderController.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── db │ │ │ └── migration │ │ │ ├── V1__create_products_table.sql │ │ │ ├── V2__add_books_data.sql │ │ │ └── V3__create_order_tables.sql │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── bookstore │ │ │ ├── ApplicationTests.java │ │ │ ├── ModularityTests.java │ │ │ ├── catalog │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── ProductControllerTests.java │ │ │ └── orders │ │ │ └── web │ │ │ └── controllers │ │ │ └── OrderControllerTests.java │ │ └── resources │ │ └── test-products-data.sql ├── monolith │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── sivalabs │ │ │ │ └── bookstore │ │ │ │ ├── Application.java │ │ │ │ ├── ApplicationProperties.java │ │ │ │ ├── catalog │ │ │ │ ├── domain │ │ │ │ │ ├── Product.java │ │ │ │ │ ├── ProductEntity.java │ │ │ │ │ ├── ProductNotFoundException.java │ │ │ │ │ ├── ProductRepository.java │ │ │ │ │ └── ProductService.java │ │ │ │ └── web │ │ │ │ │ └── controllers │ │ │ │ │ └── ProductController.java │ │ │ │ ├── common │ │ │ │ └── models │ │ │ │ │ └── PagedResult.java │ │ │ │ ├── config │ │ │ │ └── GlobalExceptionHandler.java │ │ │ │ ├── inventory │ │ │ │ └── InventoryService.java │ │ │ │ └── orders │ │ │ │ ├── domain │ │ │ │ ├── InvalidOrderException.java │ │ │ │ ├── OrderEntity.java │ │ │ │ ├── OrderMapper.java │ │ │ │ ├── OrderNotFoundException.java │ │ │ │ ├── OrderRepository.java │ │ │ │ ├── OrderService.java │ │ │ │ ├── events │ │ │ │ │ └── OrderCreatedEvent.java │ │ │ │ └── models │ │ │ │ │ ├── CreateOrderRequest.java │ │ │ │ │ ├── CreateOrderResponse.java │ │ │ │ │ ├── Customer.java │ │ │ │ │ ├── OrderDTO.java │ │ │ │ │ ├── OrderItem.java │ │ │ │ │ ├── OrderStatus.java │ │ │ │ │ └── OrderSummary.java │ │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── OrderController.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── db │ │ │ └── migration │ │ │ ├── V1__create_products_table.sql │ │ │ ├── V2__add_books_data.sql │ │ │ └── V3__create_order_tables.sql │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── bookstore │ │ │ ├── ApplicationTests.java │ │ │ ├── catalog │ │ │ └── web │ │ │ │ └── controllers │ │ │ │ └── ProductControllerTests.java │ │ │ └── orders │ │ │ └── web │ │ │ └── controllers │ │ │ └── OrderControllerTests.java │ │ └── resources │ │ └── test-products-data.sql ├── mvnw ├── mvnw.cmd └── pom.xml ├── spring-boot-openapi-swagger ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── myapp │ │ │ ├── SpringBootOpenapiSwaggerApplication.java │ │ │ ├── bookmarks │ │ │ ├── Bookmark.java │ │ │ ├── BookmarkController.java │ │ │ ├── BookmarkNotFoundException.java │ │ │ ├── BookmarkRepository.java │ │ │ ├── BookmarkService.java │ │ │ └── TagController.java │ │ │ ├── config │ │ │ ├── AppConfig.java │ │ │ ├── ApplicationProperties.java │ │ │ ├── ProblemSecurityAdvice.java │ │ │ ├── SwaggerConfig.java │ │ │ └── WebSecurityConfig.java │ │ │ ├── security │ │ │ ├── SecurityUserDetailsService.java │ │ │ ├── TokenAuthenticationFilter.java │ │ │ ├── TokenBasedAuthentication.java │ │ │ └── TokenHelper.java │ │ │ └── users │ │ │ ├── AuthenticationController.java │ │ │ ├── AuthenticationRequest.java │ │ │ ├── AuthenticationResponse.java │ │ │ ├── RoleEnum.java │ │ │ ├── User.java │ │ │ └── UserRepository.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ ├── V1__SchemaInit.sql │ │ ├── V2__Add_Users.sql │ │ └── V3__Add_Bookmarks.sql │ └── test │ └── java │ └── com │ └── sivalabs │ └── myapp │ └── SpringBootOpenapiSwaggerApplicationTests.java ├── spring-boot-rest-api-antipatterns ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── myapp │ │ │ ├── Application.java │ │ │ ├── entities │ │ │ └── Person.java │ │ │ ├── models │ │ │ └── LoginRequest.java │ │ │ ├── repositories │ │ │ └── PersonRepository.java │ │ │ ├── services │ │ │ └── PersonService.java │ │ │ └── web │ │ │ ├── LoginController.java │ │ │ └── PersonController.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── sivalabs │ └── myapp │ ├── ApplicationTests.java │ ├── repositories │ ├── PersonRepositoryTest.java │ └── PersonRepositoryTests.java │ └── web │ └── PersonControllerTest.java ├── spring-boot-testcontainers-devmode ├── .github │ └── workflows │ │ ├── gradle.yml │ │ └── maven.yml ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── .sdkmanrc ├── README.md ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mvnw ├── mvnw.cmd ├── pom.xml ├── settings.gradle.kts └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── boottcdevmode │ │ │ ├── Application.java │ │ │ ├── api │ │ │ └── ProductController.java │ │ │ └── domain │ │ │ ├── Product.java │ │ │ ├── ProductEventListener.java │ │ │ ├── ProductEventPublisher.java │ │ │ └── ProductRepository.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ ├── V1__SchemaInit.sql │ │ └── V2__SampleData.sql │ └── test │ ├── java │ └── com │ │ └── sivalabs │ │ └── boottcdevmode │ │ ├── ApplicationTests.java │ │ ├── TestApplication.java │ │ ├── TestcontainersConfig.java │ │ └── api │ │ ├── ProductControllerIT.java │ │ └── ProductControllerTcConfigIT.java │ └── resources │ └── logback-test.xml ├── spring-boot-testcontainers ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── sivalabs │ │ │ └── tcdemo │ │ │ ├── SpringBootTestcontainersApplication.java │ │ │ ├── domain │ │ │ ├── Customer.java │ │ │ ├── CustomerRepository.java │ │ │ ├── CustomerService.java │ │ │ ├── GithubService.java │ │ │ ├── Product.java │ │ │ ├── ProductRepository.java │ │ │ └── ProductService.java │ │ │ └── rest │ │ │ ├── CustomerController.java │ │ │ ├── GithubController.java │ │ │ └── ProductController.java │ └── resources │ │ ├── application.properties │ │ └── db │ │ └── migration │ │ └── V1__SchemaInit.sql │ └── test │ ├── java │ └── com │ │ └── sivalabs │ │ └── tcdemo │ │ ├── domain │ │ ├── ProductRepositoryInMemoryTest.java │ │ ├── ProductRepositoryManualStartTest.java │ │ ├── ProductRepositoryTest.java │ │ ├── ProductRepositoryWithInitializerTest.java │ │ ├── ProductRepositoryWithTCJdbcUrlTest.java │ │ ├── ProductServiceTest.java │ │ └── ProductServiceWithMockitoExtensionTest.java │ │ ├── infra │ │ ├── MockServerContainerInitializer.java │ │ └── PostgresDatabaseContainerInitializer.java │ │ └── rest │ │ ├── GithubControllerIntegrationTest.java │ │ ├── ProductControllerIntegrationTest.java │ │ └── ProductControllerTest.java │ └── resources │ ├── logback-test.xml │ └── testcontainers.properties └── spring-boot-testing-types ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── sivalabs │ │ └── myapp │ │ ├── SpringBootTestingTypesApplication.java │ │ ├── domain │ │ ├── Customer.java │ │ ├── CustomerRepository.java │ │ ├── CustomerService.java │ │ ├── Product.java │ │ ├── ProductRepository.java │ │ └── ProductService.java │ │ └── rest │ │ ├── CustomerController.java │ │ └── ProductController.java └── resources │ ├── application.properties │ └── db │ └── migration │ └── V1__SchemaInit.sql └── test ├── java └── com │ └── sivalabs │ └── myapp │ ├── SpringBootTestingTypesApplicationTests.java │ ├── domain │ ├── ProductRepositoryTest.java │ ├── ProductServiceTest.java │ └── ProductServiceWithMockitoExtensionTest.java │ └── rest │ ├── ProductControllerIntegrationTest.java │ └── ProductControllerTest.java └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SivaLabs YouTube Code Samples 2 | 3 | * [SivaLabs YouTube Channel](https://www.youtube.com/c/SivaLabs) 4 | * [SivaLabs Blog](https://sivalabs.in) 5 | * [SivaLabs Twitter](https://twitter.com/sivalabs) 6 | * [SivaLabs GitHub](https://github.com/sivaprasadreddy?tab=repositories) 7 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/clone-and-run-devexp-using-testcontainers/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=17.0.6-tem 2 | maven=3.9.4 3 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 K. Siva Prasad Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/clone-and-run-devexp-using-testcontainers/app.png -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/java/com/sivalabs/demo/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/java/com/sivalabs/demo/api/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo.api; 2 | 3 | import com.sivalabs.demo.domain.Product; 4 | import com.sivalabs.demo.domain.ProductRepository; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/api/products") 13 | class ProductController { 14 | private final ProductRepository repository; 15 | 16 | ProductController(ProductRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | @GetMapping("/{code}") 21 | ResponseEntity getById(@PathVariable String code) { 22 | return repository.findByCode(code).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound() 23 | .build()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/java/com/sivalabs/demo/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo.domain; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Modifying; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | public interface ProductRepository extends JpaRepository { 10 | 11 | @Modifying 12 | @Query("update Product p set p.price = ?2 where p.code = ?1") 13 | void updateProductPrice(String code, BigDecimal price); 14 | 15 | Optional findByCode(String code); 16 | } 17 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/java/com/sivalabs/demo/events/ProductEventListener.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo.events; 2 | 3 | import com.sivalabs.demo.domain.ProductRepository; 4 | import java.math.BigDecimal; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | @Transactional 11 | public class ProductEventListener { 12 | public static final String PRODUCT_PRICE_CHANGES_TOPIC = "product-price-changes"; 13 | private final ProductRepository productRepository; 14 | 15 | public ProductEventListener(ProductRepository productRepository) { 16 | this.productRepository = productRepository; 17 | } 18 | 19 | @KafkaListener(topics = PRODUCT_PRICE_CHANGES_TOPIC, groupId = "demo") 20 | public void handle(ProductPriceChangedEvent event) { 21 | productRepository.updateProductPrice(event.productCode(), event.price()); 22 | } 23 | 24 | public record ProductPriceChangedEvent(String productCode, BigDecimal price) {} 25 | } 26 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.show-sql=true 2 | ######## Kafka Configuration ######### 3 | #spring.kafka.bootstrap-servers=localhost:9092 4 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 5 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 6 | 7 | spring.kafka.consumer.group-id=demo 8 | spring.kafka.consumer.auto-offset-reset=latest 9 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 10 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 11 | spring.kafka.consumer.properties.spring.json.trusted.packages=* 12 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/main/resources/db/migration/V1__init.sql: -------------------------------------------------------------------------------- 1 | create table products ( 2 | id BIGSERIAL PRIMARY KEY, 3 | code varchar not null UNIQUE, 4 | name varchar not null, 5 | price numeric(5,2) not null 6 | ); 7 | 8 | insert into products(code, name, price) values ('P100', 'Product One', 200); 9 | insert into products(code, name, price) values ('P200', 'Product Two', 350); 10 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/test/java/com/sivalabs/demo/ContainersConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import org.springframework.boot.devtools.restart.RestartScope; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 6 | import org.springframework.context.annotation.Bean; 7 | import org.testcontainers.containers.KafkaContainer; 8 | import org.testcontainers.containers.PostgreSQLContainer; 9 | import org.testcontainers.utility.DockerImageName; 10 | 11 | @TestConfiguration(proxyBeanMethods = false) 12 | public class ContainersConfig { 13 | 14 | @Bean 15 | @ServiceConnection 16 | @RestartScope 17 | PostgreSQLContainer postgres() { 18 | return new PostgreSQLContainer<>("postgres:15-alpine").withReuse(true); 19 | } 20 | 21 | @Bean 22 | @ServiceConnection 23 | @RestartScope 24 | KafkaContainer kafka() { 25 | return new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0")).withReuse(true); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/test/java/com/sivalabs/demo/TestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | 5 | public class TestApplication { 6 | public static void main(String[] args) { 7 | SpringApplication.from(Application::main).with(ContainersConfig.class).run(args); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.kafka.consumer.auto-offset-reset=earliest -------------------------------------------------------------------------------- /clone-and-run-devexp-using-testcontainers/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /spring-boot-actuator/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-actuator/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-actuator/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-actuator/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | 10 | @Entity 11 | @Table(name = "customers") 12 | @Setter 13 | @Getter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class Customer { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_id_generator") 20 | @SequenceGenerator(name = "customer_id_generator", sequenceName = "customer_id_seq") 21 | private Long id; 22 | 23 | @Column(nullable = false) 24 | private String name; 25 | 26 | @Column(nullable = false, unique = true) 27 | private String email; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import javax.validation.Valid; 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/api/customers") 14 | @RequiredArgsConstructor 15 | public class CustomerController { 16 | private final CustomerService customerService; 17 | 18 | @GetMapping 19 | public List getAllCustomers() { 20 | return customerService.getAllCustomers(); 21 | } 22 | 23 | @GetMapping("/{id}") 24 | public ResponseEntity getCustomerById(@PathVariable Long id) { 25 | Customer customer = customerService.getCustomerById(id); 26 | return ResponseEntity.ok(customer); 27 | } 28 | 29 | @PostMapping 30 | @ResponseStatus(HttpStatus.CREATED) 31 | public Customer createCustomer(@RequestBody @Valid Customer customer) { 32 | return customerService.createCustomer(customer); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class CustomerNotFoundException extends RuntimeException { 8 | 9 | public CustomerNotFoundException(Long customerId) { 10 | super("Customer with id="+customerId+" not found"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | interface CustomerRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.cache.annotation.Cacheable; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | @Transactional 15 | @RequiredArgsConstructor 16 | public class CustomerService { 17 | private final Logger log = LoggerFactory.getLogger(CustomerService.class); 18 | 19 | private final CustomerRepository customerRepository; 20 | 21 | @Transactional(readOnly = true) 22 | public List getAllCustomers() { 23 | log.debug("----getAllCustomers----"); 24 | return customerRepository.findAll(); 25 | } 26 | 27 | public Customer createCustomer(Customer customer) { 28 | return customerRepository.save(customer); 29 | } 30 | 31 | @Transactional(readOnly = true) 32 | @Cacheable(cacheNames = {"customer-by-id"}) 33 | public Customer getCustomerById(Long id) { 34 | log.debug("Get customer by id: {}", id); 35 | return customerRepository.findById(id) 36 | .orElseThrow(() -> new CustomerNotFoundException(id)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/DataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @RequiredArgsConstructor 9 | public class DataInitializer implements CommandLineRunner { 10 | private final CustomerService customerService; 11 | 12 | @Override 13 | public void run(String... args) { 14 | customerService.createCustomer(new Customer(null, "Siva", "siva@gmail.com")); 15 | customerService.createCustomer(new Customer(null, "Neha", "neha@gmail.com")); 16 | } 17 | } -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/MyScheduledJobs.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.scheduling.annotation.Scheduled; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Component 10 | @Slf4j 11 | public class MyScheduledJobs { 12 | 13 | @Scheduled(fixedRate = 30000) 14 | public void every30SecondsJob() { 15 | log.info("Every30seconds job is running at "+ LocalDateTime.now()); 16 | } 17 | 18 | @Scheduled(cron = "0 * * * * MON-FRI") 19 | public void everyMinuteJob() { 20 | log.info("everyMinuteJob job is running at "+ LocalDateTime.now()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/java/com/sivalabs/myapp/SpringBootActuatorApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | @SpringBootApplication 9 | @EnableCaching 10 | @EnableScheduling 11 | public class SpringBootActuatorApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SpringBootActuatorApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.boot.admin.client.url=http://localhost:9090 2 | 3 | management.endpoints.web.exposure.include=* 4 | management.endpoint.health.show-components=always 5 | management.endpoint.health.show-details=always 6 | 7 | management.info.env.enabled=true 8 | management.info.java.enabled=true 9 | management.info.os.enabled=true 10 | 11 | info.myapp.name=ActuatorDemo 12 | info.myapp.version=1.0.1 13 | info.myapp.author=SivaLabs 14 | info.myapp.website=https://sivalabs.in 15 | 16 | info.app.encoding=@project.build.sourceEncoding@ 17 | info.app.java.source=@java.version@ 18 | info.app.java.target=@java.version@ 19 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/resources/db/migration/V1__SchemaInit.sql: -------------------------------------------------------------------------------- 1 | create sequence customer_id_seq start with 1 increment by 50; 2 | 3 | create table customers 4 | ( 5 | id bigint DEFAULT nextval('customer_id_seq') not null, 6 | email varchar(255) not null, 7 | name varchar(255) not null, 8 | primary key (id) 9 | ); 10 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/main/resources/db/migration/V2__Add_Users_Table.sql: -------------------------------------------------------------------------------- 1 | create sequence user_id_seq start with 1 increment by 50; 2 | 3 | create table users 4 | ( 5 | id bigint DEFAULT nextval('user_id_seq') not null, 6 | email varchar(255) not null, 7 | name varchar(255) not null, 8 | primary key (id) 9 | ); 10 | -------------------------------------------------------------------------------- /spring-boot-actuator/src/test/java/com/sivalabs/myapp/SpringBootActuatorApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootActuatorApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-admin-server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-admin-server/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-admin-server/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-admin-server/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-admin-server/src/main/java/com/sivalabs/adminserver/SpringBootAdminServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.adminserver; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableAdminServer 9 | public class SpringBootAdminServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SpringBootAdminServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-admin-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | -------------------------------------------------------------------------------- /spring-boot-admin-server/src/test/java/com/sivalabs/adminserver/SpringBootAdminServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.adminserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootAdminServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-docker-compose-demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Docker Compose Support 2 | 3 | This project is a simple example of how to use Docker Compose with Spring Boot. 4 | 5 | [![Spring Boot Docker Compose Support](https://img.youtube.com/vi/PZt5EJTLH4o/0.jpg)](https://www.youtube.com/watch?v=PZt5EJTLH4o) 6 | 7 | ## Requirements 8 | * JDK 17 or higher 9 | * Docker and Docker Compose 10 | 11 | ## Running the application 12 | 1. Clone the repository 13 | 2. Run Application.java in your IDE 14 | 3. or run the following command in the project root directory: 15 | ```shell 16 | ./mvnw spring-boot:run 17 | ``` 18 | 4. Run tests with the following command: 19 | ```shell 20 | ./mvnw test 21 | ``` -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | mariadb: 3 | image: 'mariadb:11.3.2' 4 | container_name: mariadb 5 | environment: 6 | - 'MARIADB_DATABASE=myapp' 7 | - 'MARIADB_PASSWORD=secret' 8 | - 'MARIADB_ROOT_PASSWORD=admin' 9 | - 'MARIADB_USER=siva' 10 | ports: 11 | - '3306' 12 | 13 | zookeeper: 14 | image: confluentinc/cp-zookeeper:7.6.0 15 | hostname: zookeeper 16 | container_name: zookeeper 17 | ports: 18 | - "2181:2181" 19 | environment: 20 | ZOOKEEPER_CLIENT_PORT: 2181 21 | ZOOKEEPER_SERVER_ID: 1 22 | 23 | kafka: 24 | image: confluentinc/cp-kafka:7.6.0 25 | hostname: kafka 26 | container_name: kafka 27 | ports: 28 | - "9092:9092" 29 | - "19092:9092" 30 | environment: 31 | KAFKA_BROKER_ID: 1 32 | KAFKA_LISTENERS: INTERNAL://:9092,EXTERNAL://:19092 33 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://localhost:9092,EXTERNAL://localhost:19092 34 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT 35 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 36 | KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" 37 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 38 | depends_on: 39 | - zookeeper -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/java/com/sivalabs/demo/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/java/com/sivalabs/demo/Bookmark.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import java.time.Instant; 4 | 5 | public record Bookmark( 6 | Long id, 7 | String title, 8 | String url, 9 | Instant createdAt) { 10 | } -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/java/com/sivalabs/demo/BookmarkCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import java.time.Instant; 4 | 5 | public record BookmarkCreatedEvent( 6 | String eventId, 7 | String title, 8 | String url, 9 | Instant createdAt) { 10 | } -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/java/com/sivalabs/demo/BookmarkEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class BookmarkEventHandler { 10 | private static final Logger log = LoggerFactory.getLogger(BookmarkEventHandler.class); 11 | 12 | @KafkaListener(topics = "demo-topic") 13 | public void handle(BookmarkCreatedEvent event) { 14 | log.info("Received BookmarkCreatedEvent:{}: ", event); 15 | } 16 | } -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/java/com/sivalabs/demo/CreateBookmarkRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.demo; 2 | 3 | public record CreateBookmarkRequest( 4 | String title, 5 | String url) { 6 | } -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-boot-docker-compose-demo 2 | spring.docker.compose.lifecycle-management=start_only 3 | spring.docker.compose.skip.in-tests=false 4 | 5 | ######## Kafka Configuration ######### 6 | KAFKA_BROKER=localhost:9092 7 | spring.kafka.bootstrap-servers=${KAFKA_BROKER} 8 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 9 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 10 | spring.kafka.producer.properties.spring.json.add.type.headers=true 11 | 12 | spring.kafka.consumer.group-id=${spring.application.name} 13 | spring.kafka.consumer.auto-offset-reset=latest 14 | #spring.kafka.consumer.auto-offset-reset=earliest 15 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 16 | #spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 17 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.ErrorHandlingDeserializer 18 | spring.kafka.consumer.properties.spring.deserializer.value.delegate.class=org.springframework.kafka.support.serializer.JsonDeserializer 19 | spring.kafka.consumer.properties.spring.json.trusted.packages=* 20 | -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/main/resources/db/migration/V1__bookmarks_table.sql: -------------------------------------------------------------------------------- 1 | create sequence bookmark_id_seq start with 1000 increment by 50; 2 | 3 | create table bookmarks ( 4 | id bigint not null DEFAULT NEXT VALUE FOR bookmark_id_seq, 5 | title varchar(100) not null, 6 | url varchar(100) not null, 7 | created_at timestamp, 8 | updated_at timestamp, 9 | primary key (id) 10 | ); 11 | 12 | INSERT INTO bookmarks(title, url, created_at) VALUES 13 | ('How (not) to ask for Technical Help?','https://sivalabs.in/how-to-not-to-ask-for-technical-help', CURRENT_TIMESTAMP), 14 | ('Getting Started with Kubernetes','https://sivalabs.in/getting-started-with-kubernetes', CURRENT_TIMESTAMP), 15 | ('Few Things I learned in the HardWay in 15 years of my career','https://sivalabs.in/few-things-i-learned-the-hardway-in-15-years-of-my-career', CURRENT_TIMESTAMP), 16 | ('All the resources you ever need as a Java & Spring application developer','https://sivalabs.in/all-the-resources-you-ever-need-as-a-java-spring-application-developer', CURRENT_TIMESTAMP), 17 | ('SpringBoot Integration Testing using Testcontainers Starter','https://sivalabs.in/spring-boot-integration-testing-using-testcontainers-starter', CURRENT_TIMESTAMP), 18 | ('Testing SpringBoot Applications','https://sivalabs.in/spring-boot-testing', CURRENT_TIMESTAMP) 19 | ; -------------------------------------------------------------------------------- /spring-boot-docker-compose-demo/src/test/resources/test-data.sql: -------------------------------------------------------------------------------- 1 | TRUNCATE TABLE bookmarks; 2 | 3 | INSERT INTO bookmarks(id,title, url, created_at) VALUES 4 | (1, 'How (not) to ask for Technical Help?','https://sivalabs.in/how-to-not-to-ask-for-technical-help', CURRENT_TIMESTAMP), 5 | (2, 'Getting Started with Kubernetes','https://sivalabs.in/getting-started-with-kubernetes', CURRENT_TIMESTAMP), 6 | (3, 'Few Things I learned in the HardWay in 15 years of my career','https://sivalabs.in/few-things-i-learned-the-hardway-in-15-years-of-my-career', CURRENT_TIMESTAMP), 7 | (4, 'All the resources you ever need as a Java & Spring application developer','https://sivalabs.in/all-the-resources-you-ever-need-as-a-java-spring-application-developer', CURRENT_TIMESTAMP), 8 | (5, 'SpringBoot Integration Testing using Testcontainers Starter','https://sivalabs.in/spring-boot-integration-testing-using-testcontainers-starter', CURRENT_TIMESTAMP), 9 | (6, 'Testing SpringBoot Applications','https://sivalabs.in/spring-boot-testing', CURRENT_TIMESTAMP) 10 | ; -------------------------------------------------------------------------------- /spring-boot-exception-handling/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-exception-handling/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-exception-handling/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/ExceptionHandling.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import com.sivalabs.myapp.domain.CustomerNotFoundException; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestControllerAdvice; 7 | import org.springframework.web.context.request.NativeWebRequest; 8 | import org.zalando.problem.Problem; 9 | import org.zalando.problem.Status; 10 | import org.zalando.problem.ThrowableProblem; 11 | import org.zalando.problem.spring.web.advice.ProblemHandling; 12 | 13 | @RestControllerAdvice 14 | class ExceptionHandling implements ProblemHandling { 15 | 16 | @ExceptionHandler 17 | public ResponseEntity handle(CustomerNotFoundException e, NativeWebRequest request) { 18 | ThrowableProblem problem = Problem.builder().withStatus(Status.NOT_FOUND) 19 | .withTitle("Customer Not Found") 20 | .withDetail(e.getMessage()) 21 | .build(); 22 | return create(problem, request); 23 | } 24 | } -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/SpringBootExceptionHandlingApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import com.sivalabs.myapp.domain.Customer; 4 | import com.sivalabs.myapp.domain.CustomerService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.stereotype.Component; 10 | 11 | @SpringBootApplication 12 | public class SpringBootExceptionHandlingApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(SpringBootExceptionHandlingApplication.class, args); 16 | } 17 | 18 | } 19 | 20 | @Component 21 | @RequiredArgsConstructor 22 | class DataInitializer implements CommandLineRunner { 23 | private final CustomerService customerService; 24 | 25 | @Override 26 | public void run(String... args) { 27 | customerService.createCustomer(new Customer(null, "Siva", "siva@gmail.com")); 28 | customerService.createCustomer(new Customer(null, "Neha", "neha@gmail.com")); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import javax.validation.constraints.Email; 10 | import javax.validation.constraints.NotEmpty; 11 | 12 | @Entity 13 | @Table(name = "customers") 14 | @Setter 15 | @Getter 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class Customer { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 21 | private Long id; 22 | 23 | @Column(nullable = false) 24 | @NotEmpty(message = "Name can not be empty") 25 | private String name; 26 | 27 | @Column(nullable = false, unique = true) 28 | @NotEmpty(message = "Email can not be empty") 29 | @Email(message = "Email is invalid") 30 | private String email; 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/domain/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | public CustomerNotFoundException(Long customerId) { 6 | super("Customer with id="+customerId+" not found"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/domain/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | interface CustomerRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/domain/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import java.util.List; 8 | 9 | @Service 10 | @Transactional 11 | @RequiredArgsConstructor 12 | public class CustomerService { 13 | private final CustomerRepository customerRepository; 14 | 15 | @Transactional(readOnly = true) 16 | public List getAllCustomers() { 17 | return customerRepository.findAll(); 18 | } 19 | 20 | public Customer createCustomer(Customer customer) { 21 | return customerRepository.save(customer); 22 | } 23 | 24 | public Customer getCustomerById(Long id) { 25 | return customerRepository.findById(id) 26 | .orElseThrow(() -> new CustomerNotFoundException(id)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/domain/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Getter 12 | public class ErrorResponse { 13 | private String message; 14 | private LocalDateTime timestamp; 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/java/com/sivalabs/myapp/rest/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.rest; 2 | 3 | import com.sivalabs.myapp.domain.CustomerNotFoundException; 4 | import com.sivalabs.myapp.domain.ErrorResponse; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/api/orders") 14 | public class OrderController { 15 | 16 | @GetMapping() 17 | public List getCustomerOrders(@RequestParam Long customerId) { 18 | throw new CustomerNotFoundException(customerId); 19 | } 20 | 21 | @ExceptionHandler({CustomerNotFoundException.class}) 22 | public ResponseEntity handleCustomerNotFoundException(CustomerNotFoundException e) { 23 | ErrorResponse errorResponse = new ErrorResponse("No orders found for given customer Id", LocalDateTime.now()); 24 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #server.error.include-binding-errors=always 2 | #server.error.include-exception=true 3 | #server.error.include-message=always 4 | #server.error.include-stacktrace=never 5 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/resources/templates/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | 7 |

Welcome

8 | 9 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Server Error Encountered

5 |
Timestamp: timestamp
6 |
Status: status
7 |
Error: error
8 |

Message: message

9 |
Stacktrace:

trace

10 | 11 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/resources/templates/error/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

400 - BadRequest

4 |
Timestamp: timestamp
5 |
Status: status
6 |
Error: error
7 |

Message: message

8 |
Stacktrace:

trace

9 | 10 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/main/resources/templates/error/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

403 - AccessDenied

4 |
Timestamp: timestamp
5 |
Status: status
6 |
Error: error
7 |

Message: message

8 |
Stacktrace:

trace

9 | 10 | -------------------------------------------------------------------------------- /spring-boot-exception-handling/src/test/java/com/sivalabs/myapp/SpringBootExceptionHandlingApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootExceptionHandlingApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-htmx-demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-htmx-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Thymeleaf HTMX Tutorial 2 | 3 | This project is a simple example of how to use HTMX with Spring Boot. 4 | 5 | [![Spring Boot Thymeleaf HTMX Tutorial](https://img.youtube.com/vi/T6dU3GZf6DA/0.jpg)](https://www.youtube.com/watch?v=T6dU3GZf6DA) 6 | 7 | ## Requirements 8 | * JDK 17 or higher 9 | * Docker and Docker Compose 10 | * To use Spring AI OpenAI support, you need to configure the OPENAI_API_KEY environment variable. 11 | 12 | ## Running the application 13 | 1. Clone the repository 14 | 2. Run Application.java in your IDE 15 | 3. or run the following command in the project root directory: 16 | ```shell 17 | ./mvnw spring-boot:run 18 | ``` 19 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: 'postgres:16' 4 | environment: 5 | - 'POSTGRES_DB=postgres' 6 | - 'POSTGRES_PASSWORD=postgres' 7 | - 'POSTGRES_USER=postgres' 8 | ports: 9 | - '5432:5432' 10 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.bind.DefaultValue; 6 | import org.springframework.validation.annotation.Validated; 7 | 8 | @Validated 9 | @ConfigurationProperties(prefix = "bookstore") 10 | public record ApplicationProperties(@DefaultValue("10") @Min(1) int pageSize) {} 11 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/catalog/domain/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import org.springframework.data.domain.Page; 4 | 5 | import java.util.List; 6 | 7 | public record PagedResult( 8 | List data, 9 | long totalElements, 10 | int pageNumber, 11 | int totalPages, 12 | boolean isFirst, 13 | boolean isLast, 14 | boolean hasNext, 15 | boolean hasPrevious) { 16 | 17 | public PagedResult(Page page) { 18 | this( 19 | page.getContent(), 20 | page.getTotalElements(), 21 | page.getNumber() + 1, 22 | page.getTotalPages(), 23 | page.isFirst(), 24 | page.isLast(), 25 | page.hasNext(), 26 | page.hasPrevious() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/catalog/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record Product( 6 | String code, 7 | String name, 8 | String description, 9 | String imageUrl, 10 | BigDecimal price) { 11 | public String getDisplayName() { 12 | if(name.length() <= 20) return name; 13 | return name.substring(0, 20) + "..."; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | class ProductMapper { 4 | 5 | static Product toProduct(ProductEntity productEntity) { 6 | return new Product( 7 | productEntity.getCode(), 8 | productEntity.getName(), 9 | productEntity.getDescription(), 10 | productEntity.getImageUrl(), 11 | productEntity.getPrice()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | public ProductNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static ProductNotFoundException forCode(String code) { 9 | return new ProductNotFoundException("Product with code " + code + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | interface ProductRepository extends JpaRepository { 8 | Optional findByCode(String code); 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/InvalidOrderException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class InvalidOrderException extends RuntimeException { 4 | 5 | public InvalidOrderException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/OrderItemEntity.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.SequenceGenerator; 11 | import jakarta.persistence.Table; 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | 15 | import java.math.BigDecimal; 16 | 17 | @Entity 18 | @Table(name = "order_items") 19 | @Setter 20 | @Getter 21 | class OrderItemEntity { 22 | 23 | @Id 24 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_item_id_generator") 25 | @SequenceGenerator(name = "order_item_id_generator", sequenceName = "order_item_id_seq") 26 | private Long id; 27 | 28 | @Column(nullable = false) 29 | private String code; 30 | 31 | private String name; 32 | 33 | @Column(nullable = false) 34 | private BigDecimal price; 35 | 36 | @Column(nullable = false) 37 | private Integer quantity; 38 | 39 | @ManyToOne(optional = false) 40 | @JoinColumn(name = "order_id") 41 | private OrderEntity order; 42 | } 43 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class OrderNotFoundException extends RuntimeException { 4 | public OrderNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static OrderNotFoundException forOrderNumber(String orderNumber) { 9 | return new OrderNotFoundException("Order with Number " + orderNumber + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/OrderStatusEnquiryFn.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.context.annotation.Description; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.function.Function; 9 | 10 | @Service("orderStatusEnquiryFn") 11 | @Description("Get the order status for a given order number") 12 | @RequiredArgsConstructor 13 | @Slf4j 14 | class OrderStatusEnquiryFn implements 15 | Function { 16 | private final SecurityService securityService; 17 | private final OrderRepository orderRepository; 18 | 19 | @Override 20 | public Response apply(Request request) { 21 | log.info("Get order status for OrderNumber: {}", request.orderNumber()); 22 | String userName = securityService.getLoginUserName(); 23 | String status = orderRepository.findByUserNameAndOrderNumber(userName, request.orderNumber()) 24 | .map(o -> o.getStatus().name()).orElse("UNKNOWN"); 25 | return new Response(status); 26 | } 27 | 28 | public record Request(String orderNumber) {} 29 | public record Response(String status) {} 30 | } 31 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/SecurityService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class SecurityService { 7 | 8 | public String getLoginUserName() { 9 | return "user"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/Address.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record Address( 6 | @NotBlank(message = "AddressLine1 is required") String addressLine1, 7 | String addressLine2, 8 | @NotBlank(message = "City is required") String city, 9 | @NotBlank(message = "State is required") String state, 10 | @NotBlank(message = "ZipCode is required") String zipCode, 11 | @NotBlank(message = "Country is required") String country) {} 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotEmpty; 5 | 6 | import java.util.Set; 7 | 8 | public record CreateOrderRequest( 9 | @Valid @NotEmpty(message = "Items cannot be empty") Set items, 10 | @Valid Customer customer, 11 | @Valid Address deliveryAddress) {} 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record CreateOrderResponse(String orderNumber) {} 4 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record Customer( 7 | @NotBlank(message = "Customer Name is required") String name, 8 | @NotBlank(message = "Customer email is required") @Email String email, 9 | @NotBlank(message = "Customer Phone number is required") String phone) {} 10 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | import java.util.Set; 8 | 9 | public record OrderDTO( 10 | String orderNumber, 11 | String user, 12 | Set items, 13 | Customer customer, 14 | Address deliveryAddress, 15 | OrderStatus status, 16 | String comments, 17 | LocalDateTime createdAt) { 18 | 19 | @JsonProperty(access = JsonProperty.Access.READ_ONLY) 20 | public BigDecimal getTotalAmount() { 21 | return items.stream() 22 | .map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity()))) 23 | .reduce(BigDecimal.ZERO, BigDecimal::add); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderForm.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | public record OrderForm(@Valid Customer customer, 6 | @Valid Address deliveryAddress) { 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record OrderItem( 10 | @NotBlank(message = "Code is required") String code, 11 | @NotBlank(message = "Name is required") String name, 12 | @NotNull(message = "Price is required") BigDecimal price, 13 | @NotNull @Min(1) Integer quantity) {} 14 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public enum OrderStatus { 4 | NEW, 5 | IN_PROCESS, 6 | DELIVERED, 7 | CANCELLED, 8 | ERROR 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderSummary.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record OrderSummary(String orderNumber, OrderStatus status) {} 4 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/java/com/sivalabs/bookstore/orders/web/controllers/ControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.web.controllers; 2 | 3 | import com.sivalabs.bookstore.orders.domain.models.Cart; 4 | import jakarta.servlet.http.HttpSession; 5 | import org.springframework.web.bind.annotation.ModelAttribute; 6 | 7 | @org.springframework.web.bind.annotation.ControllerAdvice 8 | class ControllerAdvice { 9 | 10 | @ModelAttribute("cartItemCount") 11 | public int cartItemCount(HttpSession session){ 12 | Cart cart = (Cart) session.getAttribute("cart"); 13 | return cart == null ? 0 : cart.getItemCount(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-boot-htmx-demo 2 | 3 | ######## Compose Configuration ######### 4 | spring.docker.compose.lifecycle-management=start_only 5 | 6 | ######## Database Configuration ######### 7 | spring.jpa.show-sql=true 8 | spring.jpa.open-in-view=false 9 | spring.flyway.clean-disabled=false 10 | spring.flyway.clean-on-validation-error=true 11 | 12 | ######## AI Configuration ######### 13 | spring.ai.openai.api-key=${OPENAI_API_KEY} 14 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/db/migration/V1__create_products_table.sql: -------------------------------------------------------------------------------- 1 | create sequence product_id_seq start with 1000 increment by 50; 2 | 3 | create table products 4 | ( 5 | id bigint default nextval('product_id_seq') not null, 6 | code text not null unique, 7 | name text not null, 8 | description text, 9 | image_url text, 10 | price numeric not null, 11 | primary key (id) 12 | ); 13 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/db/migration/V3__create_order_tables.sql: -------------------------------------------------------------------------------- 1 | create sequence order_id_seq start with 1000 increment by 50; 2 | create sequence order_item_id_seq start with 1000 increment by 50; 3 | 4 | create table orders 5 | ( 6 | id bigint default nextval('order_id_seq') not null, 7 | order_number text not null unique, 8 | username text not null, 9 | customer_name text not null, 10 | customer_email text not null, 11 | customer_phone text not null, 12 | delivery_address_line1 text not null, 13 | delivery_address_line2 text, 14 | delivery_address_city text not null, 15 | delivery_address_state text not null, 16 | delivery_address_zip_code text not null, 17 | delivery_address_country text not null, 18 | status text not null, 19 | comments text, 20 | created_at timestamp, 21 | updated_at timestamp, 22 | primary key (id) 23 | ); 24 | 25 | create table order_items 26 | ( 27 | id bigint default nextval('order_item_id_seq') not null, 28 | code text not null, 29 | name text not null, 30 | price numeric not null, 31 | quantity integer not null, 32 | primary key (id), 33 | order_id bigint not null references orders (id) 34 | ); 35 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/static/css/styles.css: -------------------------------------------------------------------------------- 1 | #app { 2 | padding-top: 90px; 3 | } -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/static/images/books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-htmx-demo/src/main/resources/static/images/books.png -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/cart.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |

Chat Support

10 | 11 | 18 | 19 |
20 |
22 |
23 | 24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/orders.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 |
10 |
11 |

All Orders

12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
Order IDStatus
25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/partials/cart-item-count.html: -------------------------------------------------------------------------------- 1 | Cart([[${cartItemCount}]]) -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/partials/chat-answer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/partials/orders.html: -------------------------------------------------------------------------------- 1 | 2 | OrderNumber 3 | status 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/main/resources/templates/products.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/test/java/com/sivalabs/bookstore/AbstractIT.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import io.restassured.RestAssured; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.web.server.LocalServerPort; 7 | import org.springframework.context.annotation.Import; 8 | 9 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 10 | 11 | @SpringBootTest(webEnvironment = RANDOM_PORT) 12 | @Import(ContainersConfig.class) 13 | public abstract class AbstractIT { 14 | @LocalServerPort 15 | int port; 16 | 17 | @BeforeEach 18 | void setUp() { 19 | RestAssured.port = port; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/test/java/com/sivalabs/bookstore/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | class ApplicationTests extends AbstractIT { 7 | 8 | @Test 9 | void contextLoads() { 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/test/java/com/sivalabs/bookstore/ContainersConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | import org.testcontainers.containers.PostgreSQLContainer; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | @TestConfiguration(proxyBeanMethods = false) 10 | public class ContainersConfig { 11 | @Bean 12 | @ServiceConnection 13 | PostgreSQLContainer postgresContainer() { 14 | return new PostgreSQLContainer<>(DockerImageName.parse("postgres:16")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-htmx-demo/src/test/java/com/sivalabs/bookstore/TestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 6 | import org.springframework.context.annotation.Bean; 7 | import org.testcontainers.containers.PostgreSQLContainer; 8 | import org.testcontainers.utility.DockerImageName; 9 | 10 | public class TestApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.from(Application::main) 14 | .with(ContainersConfig.class).run(args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-logging/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-logging/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-logging/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-logging/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-logging/src/main/java/com/sivalabs/myapp/DemoController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.Map; 10 | 11 | @RestController 12 | public class DemoController { 13 | private static final Logger logger = LoggerFactory.getLogger(DemoController.class); 14 | 15 | @GetMapping("/hello") 16 | public Map hello(@RequestParam(defaultValue = "World") String name) { 17 | logger.info("Name: {}", name); 18 | if(logger.isTraceEnabled()) { 19 | logger.trace("Greeting: {}", this.getGreetingMessage(name)); 20 | } 21 | return Map.of("greeting", "Hello "+ name); 22 | } 23 | 24 | private String getGreetingMessage(String name) { 25 | return "Hello "+ name; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-logging/src/main/java/com/sivalabs/myapp/MDCFilter.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.slf4j.MDC; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.filter.OncePerRequestFilter; 6 | 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | import java.util.UUID; 13 | 14 | @Component 15 | public class MDCFilter extends OncePerRequestFilter { 16 | 17 | @Override 18 | protected void doFilterInternal(HttpServletRequest request, 19 | HttpServletResponse response, 20 | FilterChain filterChain) throws ServletException, IOException { 21 | try { 22 | //String userId = request.getHeader("UserId"); 23 | String userId = UUID.randomUUID().toString(); 24 | MDC.put("UserId", userId); 25 | MDC.put("CorrelationID", UUID.randomUUID().toString()); 26 | filterChain.doFilter(request, response); 27 | } finally { 28 | //MDC.remove("UserId"); 29 | MDC.clear(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-logging/src/main/java/com/sivalabs/myapp/SpringBootLoggingApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootLoggingApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootLoggingApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-logging/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | #logging.level.root=INFO 3 | #logging.level.com.sivalabs=TRACE 4 | #logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(UserId:%X{UserId:-Guest}){magenta} %clr(Context:%X){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n 5 | #logging.file.name=myapp.log 6 | 7 | app.region=us-east-2 8 | -------------------------------------------------------------------------------- /spring-boot-logging/src/test/java/com/sivalabs/myapp/SpringBootLoggingApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootLoggingApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-missing-guide/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-missing-guide/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-missing-guide 2 | Material for SpringBoot Missing Guide YouTube Series 3 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | public class Customer { 4 | Long id; 5 | String name; 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | import org.springframework.transaction.annotation.Propagation; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | 7 | public class CustomerService implements ICustomerService { 8 | 9 | @Transactional 10 | public Customer createCustomer(Customer customer) { 11 | if(customer.id != 0) { 12 | throw new RuntimeException("Invalid Customer Id"); 13 | } 14 | System.out.println("Create customer"); 15 | updateCustomer(customer); 16 | 17 | return customer; 18 | } 19 | 20 | @Transactional(propagation = Propagation.REQUIRES_NEW) 21 | public void updateCustomer(Customer customer) { 22 | System.out.println("Update customer"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/CustomerServiceProxy.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | public class CustomerServiceProxy { 4 | private final CustomerService delegate; 5 | 6 | public CustomerServiceProxy(CustomerService delegate) { 7 | this.delegate = delegate; 8 | } 9 | 10 | public Customer createCustomer(Customer customer) { 11 | try { 12 | System.out.println("Start transaction"); 13 | Customer result = delegate.createCustomer(customer); 14 | System.out.println("Commit transaction"); 15 | return result; 16 | } catch (RuntimeException e) { 17 | System.out.println("Rollback transaction"); 18 | throw e; 19 | } finally { 20 | System.out.println("Close connection"); 21 | } 22 | } 23 | 24 | public void updateCustomer(Customer customer) { 25 | try { 26 | System.out.println("Start transaction"); 27 | delegate.updateCustomer(customer); 28 | System.out.println("Commit transaction"); 29 | } catch (RuntimeException e) { 30 | System.out.println("Rollback transaction"); 31 | throw e; 32 | } finally { 33 | System.out.println("Close connection"); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/ICustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | public interface ICustomerService { 4 | Customer createCustomer(Customer customer); 5 | void updateCustomer(Customer customer); 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/Person.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | public class Person { 4 | Long id; 5 | String name; 6 | String email; 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | public class PersonService { 4 | 5 | public Person createPerson(Person customer) { 6 | System.out.println("Create person"); 7 | return customer; 8 | } 9 | 10 | public void updatePerson(Person person) { 11 | System.out.println("Update Person"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/TransactionJdkProxy.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | public class TransactionJdkProxy implements InvocationHandler { 8 | 9 | private final T target; 10 | 11 | public TransactionJdkProxy(T target) { 12 | this.target = target; 13 | } 14 | 15 | @Override 16 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 17 | try { 18 | System.out.println("Start transaction"); 19 | Object result = method.invoke(target, args); 20 | System.out.println("Commit transaction"); 21 | return result; 22 | } catch (RuntimeException | InvocationTargetException e) { 23 | System.out.println("Rollback transaction"); 24 | throw e; 25 | } finally { 26 | System.out.println("Close connection"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/aop/TransactionalCglibProxy.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.aop; 2 | 3 | import org.springframework.cglib.proxy.Enhancer; 4 | import org.springframework.cglib.proxy.MethodInterceptor; 5 | 6 | public class TransactionalCglibProxy { 7 | 8 | public T createProxy(T object) { 9 | Enhancer enhancer = new Enhancer(); 10 | enhancer.setSuperclass(object.getClass()); 11 | enhancer.setCallback(getMethodInterceptor()); 12 | return (T)enhancer.create(); 13 | } 14 | 15 | private static MethodInterceptor getMethodInterceptor() { 16 | return (obj, method, args, proxy) -> { 17 | try { 18 | System.out.println("Start transaction"); 19 | Object result = proxy.invokeSuper(obj, args); 20 | System.out.println("Commit transaction"); 21 | return result; 22 | } catch (RuntimeException e) { 23 | System.out.println("Rollback transaction"); 24 | throw e; 25 | } finally { 26 | System.out.println("Close connection"); 27 | } 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/BackgroundVerificationService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * Verifies the loan requester credibility score by talking to an external 3rd party service. 7 | */ 8 | @Component 9 | public class BackgroundVerificationService implements IBackgroundVerificationService { 10 | /** 11 | * Calls 3rd party paid service to get credibility score 12 | * 13 | * @return score on the scale of 1 to 5 where 1 is best score and 5 being worst 14 | */ 15 | @Override 16 | public int getScore(String personUniqueId) { 17 | //talk to 3rd party 18 | return 2; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/BeanFactory.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class BeanFactory { 7 | private final Map, Object> BEANS = init(); 8 | 9 | //scan all the classes and identify beans annotated with @Component 10 | //Identify what are the dependencies for each component using Reflection 11 | 12 | private Map, Object> init() { 13 | Map, Object> beanMap = new HashMap<>(); 14 | String impl = "MockBackgroundVerificationService"; 15 | IBackgroundVerificationService bgvs = null; 16 | if(impl.equals("MockBackgroundVerificationService")) { 17 | bgvs = new MockBackgroundVerificationService(); 18 | } else { 19 | bgvs = new BackgroundVerificationService(); 20 | } 21 | LoanService loanService = new LoanService(bgvs); 22 | 23 | beanMap.put(IBackgroundVerificationService.class, bgvs); 24 | beanMap.put(LoanService.class, loanService); 25 | return beanMap; 26 | } 27 | 28 | public T getBean(Class type) { 29 | return (T)BEANS.get(type); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/IBackgroundVerificationService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | public interface IBackgroundVerificationService { 4 | int getScore(String personUniqueId); 5 | } 6 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/LoanRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | public class LoanRequest { 4 | Long reqId; 5 | String personName; 6 | String personEmail; 7 | String uniqueId; 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/LoanService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class LoanService { 7 | private IBackgroundVerificationService bvService; 8 | 9 | public LoanService(IBackgroundVerificationService bvService) { 10 | this.bvService = bvService; 11 | } 12 | 13 | public boolean applyForLoan(LoanRequest loanRequest) { 14 | int score = bvService.getScore(loanRequest.uniqueId); 15 | if(score < 3) { 16 | System.out.println("Sanction loan"); 17 | return true; 18 | } else { 19 | System.out.println("Reject loan. Reason: Poor credibility score"); 20 | return false; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/di/MockBackgroundVerificationService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | import java.util.List; 4 | 5 | public class MockBackgroundVerificationService implements IBackgroundVerificationService{ 6 | List approvedUniqueIds = List.of( 7 | "P100", 8 | "P101", 9 | "P102", 10 | "P103" 11 | ); 12 | 13 | @Override 14 | public int getScore(String personUniqueId) { 15 | if(approvedUniqueIds.contains(personUniqueId)) { 16 | return 2; 17 | } 18 | return 4; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/hello/BookmarkNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.hello; 2 | 3 | public class BookmarkNotFoundException extends RuntimeException { 4 | 5 | public BookmarkNotFoundException(Long bookmarkId) { 6 | super("Bookmark with id: "+ bookmarkId+" not found"); 7 | } 8 | } -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/sbmg/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.sbmg; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HelloController { 8 | 9 | @GetMapping("/") 10 | public String hello(){ 11 | return "HelloWorld"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/sbmg/SpringBootMissingGuideApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.sbmg; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootMissingGuideApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootMissingGuideApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/templatee/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.templatee; 2 | 3 | class Customer { 4 | Long id; 5 | String name; 6 | } -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/java/com/sivalabs/templatee/JdbcTemplateDemo.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.templatee; 2 | 3 | import org.springframework.jdbc.core.JdbcTemplate; 4 | 5 | public class JdbcTemplateDemo { 6 | JdbcTemplate jdbcTemplate; 7 | 8 | void createCustomer(Customer customer) { 9 | jdbcTemplate.update("insert into customers(name) values(?)", customer.name); 10 | } 11 | 12 | void updateCustomer(Customer customer) { 13 | jdbcTemplate.update("update customers set name = ? where id = ?", customer.name, customer.id); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | bgvservice=BackgroundVerificationService 2 | 3 | bgvservice=MockBackgroundVerificationService 4 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/test/java/com/sivalabs/di/Main.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | 6 | BeanFactory beanFactory = new BeanFactory(); 7 | IBackgroundVerificationService bgvS = beanFactory.getBean(IBackgroundVerificationService.class); 8 | System.out.println(bgvS.getScore("P200")); 9 | 10 | LoanService loanService = beanFactory.getBean(LoanService.class); 11 | 12 | LoanRequest loanRequest = new LoanRequest(); 13 | loanRequest.uniqueId = "P100"; 14 | loanRequest.personEmail = "siva@gmail.com"; 15 | boolean approved = loanService.applyForLoan(loanRequest); 16 | if(approved) { 17 | System.out.println("Loan is approved"); 18 | } else { 19 | System.out.println("Loan is rejected"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/test/java/com/sivalabs/di/SpringMain.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.di; 2 | 3 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 4 | 5 | public class SpringMain { 6 | 7 | public static void main(String[] args) { 8 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.sivalabs"); 9 | LoanService loanService = ctx.getBean(LoanService.class); 10 | LoanRequest loanRequest = new LoanRequest(); 11 | loanRequest.uniqueId = "P100"; 12 | loanRequest.personEmail = "siva@gmail.com"; 13 | boolean approved = loanService.applyForLoan(loanRequest); 14 | if(approved) { 15 | System.out.println("Loan is approved"); 16 | } else { 17 | System.out.println("Loan is rejected"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-missing-guide/src/test/java/com/sivalabs/sbmg/SpringBootMissingGuideApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.sbmg; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootMissingGuideApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-modulith-demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-modulith-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/README.md: -------------------------------------------------------------------------------- 1 | # Spring Modulith Crash Course 2 | This repository contains the code for the Spring Modulith Crash Course. 3 | 4 | [![Spring Modulith Crash Course : Building Modular Monoliths using Spring Boot](https://img.youtube.com/vi/FkP2aZiBrhg/0.jpg)](https://www.youtube.com/watch?v=FkP2aZiBrhg) 5 | 6 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-modulith-demo/modulith/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.bind.DefaultValue; 6 | import org.springframework.validation.annotation.Validated; 7 | 8 | @Validated 9 | @ConfigurationProperties(prefix = "bookstore") 10 | public record ApplicationProperties(@DefaultValue("10") @Min(1) int pageSize) {} 11 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/catalog/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record Product( 6 | String code, 7 | String name, 8 | String description, 9 | String imageUrl, 10 | BigDecimal price) { 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/catalog/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog; 2 | 3 | import com.sivalabs.bookstore.common.models.PagedResult; 4 | 5 | import java.util.Optional; 6 | 7 | public interface ProductService { 8 | PagedResult getProducts(int pageNo); 9 | 10 | Optional getByCode(String code); 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/catalog/config/CatalogExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.config; 2 | 3 | import com.sivalabs.bookstore.catalog.domain.ProductNotFoundException; 4 | import com.sivalabs.bookstore.orders.domain.InvalidOrderException; 5 | import com.sivalabs.bookstore.orders.domain.OrderNotFoundException; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ProblemDetail; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 11 | 12 | import java.time.Instant; 13 | 14 | @RestControllerAdvice 15 | class CatalogExceptionHandler extends ResponseEntityExceptionHandler { 16 | 17 | @ExceptionHandler(ProductNotFoundException.class) 18 | ProblemDetail handle(ProductNotFoundException e) { 19 | ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage()); 20 | problemDetail.setTitle("Product Not Found"); 21 | problemDetail.setProperty("timestamp", Instant.now()); 22 | return problemDetail; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | public ProductNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static ProductNotFoundException forCode(String code) { 9 | return new ProductNotFoundException("Product with code " + code + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import com.sivalabs.bookstore.catalog.Product; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.Optional; 10 | 11 | public interface ProductRepository extends JpaRepository { 12 | 13 | @Query(""" 14 | select new com.sivalabs.bookstore.catalog.Product( 15 | p.code, p.name, p.description, p.imageUrl, p.price) 16 | from ProductEntity p 17 | """) 18 | Page findAllBy(Pageable pageable); 19 | 20 | @Query(""" 21 | select new com.sivalabs.bookstore.catalog.Product( 22 | p.code, p.name, p.description, p.imageUrl, p.price) 23 | from ProductEntity p 24 | where p.code = :code 25 | """) 26 | Optional findByCode(String code); 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/common/models/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.common.models; 2 | 3 | import org.springframework.data.domain.Page; 4 | 5 | import java.util.List; 6 | 7 | public record PagedResult( 8 | List data, 9 | long totalElements, 10 | int pageNumber, 11 | int totalPages, 12 | boolean isFirst, 13 | boolean isLast, 14 | boolean hasNext, 15 | boolean hasPrevious) { 16 | 17 | public PagedResult(Page page) { 18 | this( 19 | page.getContent(), 20 | page.getTotalElements(), 21 | page.getNumber() + 1, 22 | page.getTotalPages(), 23 | page.isFirst(), 24 | page.isLast(), 25 | page.hasNext(), 26 | page.hasPrevious() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/common/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.ApplicationModule( 2 | type = ApplicationModule.Type.OPEN 3 | ) 4 | package com.sivalabs.bookstore.common; 5 | 6 | import org.springframework.modulith.ApplicationModule; -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/inventory/InventoryService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.inventory; 2 | 3 | import com.sivalabs.bookstore.orders.domain.events.OrderCreatedEvent; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | @Slf4j 12 | class InventoryService { 13 | 14 | @EventListener 15 | void on(OrderCreatedEvent event) { 16 | log.info("Update inventory for product code: {}", event.productCode()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/InvalidOrderException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class InvalidOrderException extends RuntimeException { 4 | 5 | public InvalidOrderException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import com.sivalabs.bookstore.orders.domain.models.CreateOrderRequest; 4 | import com.sivalabs.bookstore.orders.domain.models.OrderDTO; 5 | import com.sivalabs.bookstore.orders.domain.models.OrderStatus; 6 | 7 | import java.util.UUID; 8 | 9 | class OrderMapper { 10 | 11 | static OrderEntity convertToEntity(CreateOrderRequest request) { 12 | OrderEntity entity = new OrderEntity(); 13 | entity.setOrderNumber(UUID.randomUUID().toString()); 14 | entity.setStatus(OrderStatus.NEW); 15 | entity.setCustomer(request.customer()); 16 | entity.setOrderItem(request.item()); 17 | return entity; 18 | } 19 | 20 | static OrderDTO convertToDTO(OrderEntity order) { 21 | return new OrderDTO( 22 | order.getOrderNumber(), 23 | order.getOrderItem(), 24 | order.getCustomer(), 25 | order.getStatus(), 26 | order.getCreatedAt()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class OrderNotFoundException extends RuntimeException { 4 | public OrderNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static OrderNotFoundException forOrderNumber(String orderNumber) { 9 | return new OrderNotFoundException("Order with Number " + orderNumber + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import com.sivalabs.bookstore.orders.domain.models.OrderSummary; 4 | import org.springframework.data.domain.Sort; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | interface OrderRepository extends JpaRepository { 12 | @Query( 13 | """ 14 | select new com.sivalabs.bookstore.orders.domain.models.OrderSummary(o.orderNumber, o.status) 15 | from OrderEntity o 16 | """) 17 | List findAllBy(Sort sort); 18 | 19 | @Query( 20 | """ 21 | select distinct o 22 | from OrderEntity o left join fetch o.orderItem 23 | where o.orderNumber = :orderNumber 24 | """) 25 | Optional findByOrderNumber(String orderNumber); 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/events/OrderCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.events; 2 | 3 | public record OrderCreatedEvent( 4 | String orderNumber, 5 | String productCode, 6 | int quantity) { 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/events/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("order-events") 2 | package com.sivalabs.bookstore.orders.domain.events; -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | public record CreateOrderRequest( 6 | @Valid OrderItem item, 7 | @Valid Customer customer) {} 8 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record CreateOrderResponse(String orderNumber) {} 4 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record Customer( 7 | @NotBlank(message = "Customer Name is required") String name, 8 | @NotBlank(message = "Customer email is required") @Email String email, 9 | @NotBlank(message = "Customer Phone number is required") String phone) {} 10 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record OrderDTO( 6 | String orderNumber, 7 | OrderItem item, 8 | Customer customer, 9 | OrderStatus status, 10 | LocalDateTime createdAt) { 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record OrderItem( 10 | @NotBlank(message = "Code is required") String code, 11 | @NotBlank(message = "Name is required") String name, 12 | @NotNull(message = "Price is required") BigDecimal price, 13 | @NotNull @Min(1) Integer quantity) {} 14 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public enum OrderStatus { 4 | NEW, 5 | IN_PROCESS, 6 | DELIVERED, 7 | CANCELLED, 8 | ERROR 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderSummary.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record OrderSummary(String orderNumber, OrderStatus status) {} 4 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/java/com/sivalabs/bookstore/orders/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.ApplicationModule( 2 | allowedDependencies = {"catalog"} 3 | ) 4 | package com.sivalabs.bookstore.orders; 5 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=modulith 2 | management.endpoints.web.exposure.include=* 3 | spring.jpa.show-sql=true 4 | spring.jpa.open-in-view=false 5 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/resources/db/migration/V1__create_products_table.sql: -------------------------------------------------------------------------------- 1 | create sequence product_id_seq start with 100 increment by 50; 2 | 3 | create table products 4 | ( 5 | id bigint default nextval('product_id_seq') not null, 6 | code text not null unique, 7 | name text not null, 8 | description text, 9 | image_url text, 10 | price numeric not null, 11 | primary key (id) 12 | ); 13 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/main/resources/db/migration/V3__create_order_tables.sql: -------------------------------------------------------------------------------- 1 | create sequence order_id_seq start with 1000 increment by 50; 2 | 3 | create table orders 4 | ( 5 | id bigint default nextval('order_id_seq') not null, 6 | order_number text not null unique, 7 | customer_name text not null, 8 | customer_email text not null, 9 | customer_phone text not null, 10 | product_code text not null, 11 | product_name text not null, 12 | product_price text not null, 13 | quantity int not null, 14 | status text not null, 15 | comments text, 16 | created_at timestamp, 17 | updated_at timestamp, 18 | primary key (id) 19 | ); 20 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/test/java/com/sivalabs/bookstore/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 7 | 8 | @SpringBootTest(webEnvironment = RANDOM_PORT) 9 | class ApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/modulith/src/test/java/com/sivalabs/bookstore/ModularityTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.modulith.core.ApplicationModules; 5 | import org.springframework.modulith.docs.Documenter; 6 | 7 | class ModularityTests { 8 | static ApplicationModules modules = ApplicationModules.of(Application.class); 9 | 10 | @Test 11 | void verifiesModularStructure() { 12 | modules.verify(); 13 | } 14 | 15 | @Test 16 | void createModuleDocumentation() { 17 | new Documenter(modules).writeDocumentation(); 18 | } 19 | } -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-modulith-demo/monolith/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.bind.DefaultValue; 6 | import org.springframework.validation.annotation.Validated; 7 | 8 | @Validated 9 | @ConfigurationProperties(prefix = "bookstore") 10 | public record ApplicationProperties(@DefaultValue("10") @Min(1) int pageSize) {} 11 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/catalog/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record Product( 6 | String code, 7 | String name, 8 | String description, 9 | String imageUrl, 10 | BigDecimal price) { 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | public ProductNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static ProductNotFoundException forCode(String code) { 9 | return new ProductNotFoundException("Product with code " + code + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/catalog/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.catalog.domain; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.Optional; 9 | 10 | interface ProductRepository extends JpaRepository { 11 | 12 | @Query(""" 13 | select new com.sivalabs.bookstore.catalog.domain.Product( 14 | p.code, p.name, p.description, p.imageUrl, p.price) 15 | from ProductEntity p 16 | """) 17 | Page findAllBy(Pageable pageable); 18 | 19 | @Query(""" 20 | select new com.sivalabs.bookstore.catalog.domain.Product( 21 | p.code, p.name, p.description, p.imageUrl, p.price) 22 | from ProductEntity p 23 | where p.code = :code 24 | """) 25 | Optional findByCode(String code); 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/common/models/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.common.models; 2 | 3 | import org.springframework.data.domain.Page; 4 | 5 | import java.util.List; 6 | 7 | public record PagedResult( 8 | List data, 9 | long totalElements, 10 | int pageNumber, 11 | int totalPages, 12 | boolean isFirst, 13 | boolean isLast, 14 | boolean hasNext, 15 | boolean hasPrevious) { 16 | 17 | public PagedResult(Page page) { 18 | this( 19 | page.getContent(), 20 | page.getTotalElements(), 21 | page.getNumber() + 1, 22 | page.getTotalPages(), 23 | page.isFirst(), 24 | page.isLast(), 25 | page.hasNext(), 26 | page.hasPrevious() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/inventory/InventoryService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.inventory; 2 | 3 | import com.sivalabs.bookstore.orders.domain.events.OrderCreatedEvent; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | @Slf4j 12 | class InventoryService { 13 | 14 | @EventListener 15 | void on(OrderCreatedEvent event) { 16 | log.info("Update inventory for product code: {}", event.productCode()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/InvalidOrderException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class InvalidOrderException extends RuntimeException { 4 | 5 | public InvalidOrderException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import com.sivalabs.bookstore.orders.domain.models.CreateOrderRequest; 4 | import com.sivalabs.bookstore.orders.domain.models.OrderDTO; 5 | import com.sivalabs.bookstore.orders.domain.models.OrderStatus; 6 | 7 | import java.util.UUID; 8 | 9 | class OrderMapper { 10 | 11 | static OrderEntity convertToEntity(CreateOrderRequest request) { 12 | OrderEntity entity = new OrderEntity(); 13 | entity.setOrderNumber(UUID.randomUUID().toString()); 14 | entity.setStatus(OrderStatus.NEW); 15 | entity.setCustomer(request.customer()); 16 | entity.setOrderItem(request.item()); 17 | return entity; 18 | } 19 | 20 | static OrderDTO convertToDTO(OrderEntity order) { 21 | return new OrderDTO( 22 | order.getOrderNumber(), 23 | order.getOrderItem(), 24 | order.getCustomer(), 25 | order.getStatus(), 26 | order.getCreatedAt()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | public class OrderNotFoundException extends RuntimeException { 4 | public OrderNotFoundException(String message) { 5 | super(message); 6 | } 7 | 8 | public static OrderNotFoundException forOrderNumber(String orderNumber) { 9 | return new OrderNotFoundException("Order with Number " + orderNumber + " not found"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain; 2 | 3 | import com.sivalabs.bookstore.orders.domain.models.OrderSummary; 4 | import org.springframework.data.domain.Sort; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | interface OrderRepository extends JpaRepository { 12 | @Query( 13 | """ 14 | select new com.sivalabs.bookstore.orders.domain.models.OrderSummary(o.orderNumber, o.status) 15 | from OrderEntity o 16 | """) 17 | List findAllBy(Sort sort); 18 | 19 | @Query( 20 | """ 21 | select distinct o 22 | from OrderEntity o left join fetch o.orderItem 23 | where o.orderNumber = :orderNumber 24 | """) 25 | Optional findByOrderNumber(String orderNumber); 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/events/OrderCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.events; 2 | 3 | public record OrderCreatedEvent( 4 | String orderNumber, 5 | String productCode, 6 | int quantity) { 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.Valid; 4 | 5 | public record CreateOrderRequest( 6 | @Valid OrderItem item, 7 | @Valid Customer customer) {} 8 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/CreateOrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record CreateOrderResponse(String orderNumber) {} 4 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record Customer( 7 | @NotBlank(message = "Customer Name is required") String name, 8 | @NotBlank(message = "Customer email is required") @Email String email, 9 | @NotBlank(message = "Customer Phone number is required") String phone) {} 10 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record OrderDTO( 6 | String orderNumber, 7 | OrderItem item, 8 | Customer customer, 9 | OrderStatus status, 10 | LocalDateTime createdAt) { 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record OrderItem( 10 | @NotBlank(message = "Code is required") String code, 11 | @NotBlank(message = "Name is required") String name, 12 | @NotNull(message = "Price is required") BigDecimal price, 13 | @NotNull @Min(1) Integer quantity) {} 14 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public enum OrderStatus { 4 | NEW, 5 | IN_PROCESS, 6 | DELIVERED, 7 | CANCELLED, 8 | ERROR 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/java/com/sivalabs/bookstore/orders/domain/models/OrderSummary.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore.orders.domain.models; 2 | 3 | public record OrderSummary(String orderNumber, OrderStatus status) {} 4 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=monolith 2 | management.endpoints.web.exposure.include=* 3 | spring.jpa.show-sql=true 4 | spring.jpa.open-in-view=false 5 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/resources/db/migration/V1__create_products_table.sql: -------------------------------------------------------------------------------- 1 | create sequence product_id_seq start with 100 increment by 50; 2 | 3 | create table products 4 | ( 5 | id bigint default nextval('product_id_seq') not null, 6 | code text not null unique, 7 | name text not null, 8 | description text, 9 | image_url text, 10 | price numeric not null, 11 | primary key (id) 12 | ); 13 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/main/resources/db/migration/V3__create_order_tables.sql: -------------------------------------------------------------------------------- 1 | create sequence order_id_seq start with 1000 increment by 50; 2 | 3 | create table orders 4 | ( 5 | id bigint default nextval('order_id_seq') not null, 6 | order_number text not null unique, 7 | customer_name text not null, 8 | customer_email text not null, 9 | customer_phone text not null, 10 | product_code text not null, 11 | product_name text not null, 12 | product_price text not null, 13 | quantity int not null, 14 | status text not null, 15 | comments text, 16 | created_at timestamp, 17 | updated_at timestamp, 18 | primary key (id) 19 | ); 20 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/monolith/src/test/java/com/sivalabs/bookstore/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.bookstore; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 7 | 8 | @SpringBootTest(webEnvironment = RANDOM_PORT) 9 | class ApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-modulith-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | com.sivalabs 9 | spring-boot-modulith-demo 10 | 1.0-SNAPSHOT 11 | pom 12 | 13 | 14 | 21 15 | 21 16 | UTF-8 17 | 18 | 19 | monolith 20 | modulith 21 | 22 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-openapi-swagger/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/SpringBootOpenapiSwaggerApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import com.sivalabs.myapp.config.ApplicationProperties; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | 9 | @SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class) 10 | @EnableConfigurationProperties(ApplicationProperties.class) 11 | public class SpringBootOpenapiSwaggerApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SpringBootOpenapiSwaggerApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/bookmarks/Bookmark.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.bookmarks; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import javax.validation.constraints.NotEmpty; 10 | 11 | @Entity 12 | @Table(name = "bookmarks") 13 | @Setter 14 | @Getter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class Bookmark { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookmark_id_generator") 21 | @SequenceGenerator(name = "bookmark_id_generator", sequenceName = "bookmark_id_seq") 22 | private Long id; 23 | 24 | @Column(nullable = false) 25 | @NotEmpty(message = "Title can't be empty") 26 | private String title; 27 | 28 | @Column(nullable = false) 29 | @NotEmpty(message = "URL can't be empty") 30 | private String url; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/bookmarks/BookmarkNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.bookmarks; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class BookmarkNotFoundException extends RuntimeException { 8 | 9 | public BookmarkNotFoundException(Long customerId) { 10 | super("Bookmark with id="+customerId+" not found"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/bookmarks/BookmarkRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.bookmarks; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | interface BookmarkRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/bookmarks/BookmarkService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.bookmarks; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | @Transactional 13 | @RequiredArgsConstructor 14 | public class BookmarkService { 15 | private final Logger log = LoggerFactory.getLogger(BookmarkService.class); 16 | 17 | private final BookmarkRepository bookmarkRepository; 18 | 19 | @Transactional(readOnly = true) 20 | public List getAllBookmarks() { 21 | return bookmarkRepository.findAll(); 22 | } 23 | 24 | public Bookmark createBookmark(Bookmark bookmark) { 25 | return bookmarkRepository.save(bookmark); 26 | } 27 | 28 | @Transactional(readOnly = true) 29 | public Bookmark getBookmarkById(Long id) { 30 | log.debug("Get bookmark by id: {}", id); 31 | return bookmarkRepository.findById(id) 32 | .orElseThrow(() -> new BookmarkNotFoundException(id)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/bookmarks/TagController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.bookmarks; 2 | 3 | import io.swagger.v3.oas.annotations.tags.Tag; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | @Tag(name = "Bookmarks") 9 | public class TagController { 10 | 11 | @GetMapping("/api/tags") 12 | public void handle() { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class AppConfig { 10 | 11 | @Bean 12 | PasswordEncoder passwordEncoder() { 13 | return new BCryptPasswordEncoder(); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | @ConfigurationProperties(prefix = "myapp") 7 | @Data 8 | public class ApplicationProperties { 9 | private JwtConfig jwt = new JwtConfig(); 10 | 11 | @Data 12 | public static class JwtConfig { 13 | private static final Long DEFAULT_JWT_TOKEN_EXPIRES = 604_800L; 14 | 15 | private String issuer = "SivaLabs"; 16 | private String header = "Authorization"; 17 | private Long expiresIn = DEFAULT_JWT_TOKEN_EXPIRES; 18 | private String secret = ""; 19 | } 20 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/config/ProblemSecurityAdvice.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.config; 2 | 3 | import org.springframework.web.bind.annotation.ControllerAdvice; 4 | import org.zalando.problem.spring.web.advice.ProblemHandling; 5 | import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; 6 | /** 7 | TEMP FIX: https://github.com/zalando/problem-spring-web/issues/707 8 | */ 9 | @ControllerAdvice 10 | public class ProblemSecurityAdvice implements ProblemHandling, SecurityAdviceTrait { 11 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.config; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 5 | import io.swagger.v3.oas.annotations.info.Info; 6 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | @OpenAPIDefinition(info = @Info(title = "SpringBoot Tips API", version = "v1")) 11 | @SecurityScheme( 12 | name = "jwtBearerAuth", 13 | type = SecuritySchemeType.HTTP, 14 | bearerFormat = "JWT", 15 | scheme = "bearer" 16 | ) 17 | public class SwaggerConfig { 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/security/SecurityUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.security; 2 | 3 | import com.sivalabs.myapp.users.UserRepository; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | @Service("userDetailsService") 15 | @RequiredArgsConstructor 16 | public class SecurityUserDetailsService implements UserDetailsService { 17 | private final UserRepository userRepository; 18 | 19 | @Override 20 | public UserDetails loadUserByUsername(String email) { 21 | return userRepository 22 | .findByEmail(email) 23 | .map(user -> new User( 24 | user.getEmail(), 25 | user.getPassword(), 26 | List.of(new SimpleGrantedAuthority(user.getRole().name()))) 27 | ) 28 | .orElseThrow(() -> new UsernameNotFoundException("No user found with email " + email)); 29 | } 30 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/security/TokenBasedAuthentication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.security; 2 | 3 | import org.springframework.security.authentication.AbstractAuthenticationToken; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | 6 | public class TokenBasedAuthentication extends AbstractAuthenticationToken { 7 | private final String token; 8 | private final UserDetails principle; 9 | 10 | public TokenBasedAuthentication(String token, UserDetails principle) { 11 | super(principle.getAuthorities()); 12 | this.token = token; 13 | this.principle = principle; 14 | } 15 | 16 | @Override 17 | public boolean isAuthenticated() { 18 | return true; 19 | } 20 | 21 | @Override 22 | public Object getCredentials() { 23 | return token; 24 | } 25 | 26 | @Override 27 | public UserDetails getPrincipal() { 28 | return principle; 29 | } 30 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/users/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.users; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class AuthenticationRequest { 8 | @NotBlank(message = "UserName cannot be blank") 9 | private String username; 10 | 11 | @NotBlank(message = "Password cannot be blank") 12 | private String password; 13 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/users/AuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.users; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class AuthenticationResponse { 14 | 15 | @JsonProperty("access_token") 16 | private String accessToken; 17 | 18 | @JsonProperty("expires_at") 19 | private LocalDateTime expiresAt; 20 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/users/RoleEnum.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.users; 2 | 3 | public enum RoleEnum { 4 | ROLE_ADMIN, 5 | ROLE_MODERATOR, 6 | ROLE_USER 7 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/users/User.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.users; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | import javax.validation.constraints.Email; 8 | import javax.validation.constraints.NotEmpty; 9 | import javax.validation.constraints.Size; 10 | import java.io.Serializable; 11 | 12 | @Entity 13 | @Table(name = "users") 14 | @Setter 15 | @Getter 16 | public class User implements Serializable { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id_generator") 20 | @SequenceGenerator(name = "user_id_generator", sequenceName = "user_id_seq") 21 | private Long id; 22 | 23 | @Column(nullable = false) 24 | @NotEmpty() 25 | private String name; 26 | 27 | @Column(nullable = false, unique = true) 28 | @NotEmpty 29 | @Email(message = "Invalid email") 30 | private String email; 31 | 32 | @Column(nullable = false) 33 | @NotEmpty 34 | @Size(min = 4) 35 | private String password; 36 | 37 | @Column(nullable = false) 38 | @Enumerated(EnumType.STRING) 39 | private RoleEnum role; 40 | 41 | } -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/java/com/sivalabs/myapp/users/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.users; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface UserRepository extends JpaRepository { 8 | Optional findByEmail(String email); 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | myapp.jwt.issuer=SivaLabs 2 | myapp.jwt.header=Authorization 3 | myapp.jwt.expires-in=604800 4 | myapp.jwt.secret=secret1234 5 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/resources/db/migration/V1__SchemaInit.sql: -------------------------------------------------------------------------------- 1 | create sequence user_id_seq start with 1 increment by 50; 2 | create sequence bookmark_id_seq start with 1 increment by 50; 3 | 4 | create table users 5 | ( 6 | id bigint DEFAULT nextval('user_id_seq') not null, 7 | email varchar(255) not null, 8 | password varchar(255) not null, 9 | role varchar(20) not null, 10 | name varchar(255) not null, 11 | primary key (id) 12 | ); 13 | 14 | create table bookmarks 15 | ( 16 | id bigint DEFAULT nextval('bookmark_id_seq') not null, 17 | title varchar(255) not null, 18 | url varchar(255) not null, 19 | primary key (id) 20 | ); 21 | -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/resources/db/migration/V2__Add_Users.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO users (email, password, name, role) VALUES 2 | ('admin@gmail.com', '$2a$10$ZuGgeoawgOg.6AM3QEGZ3O4QlBSWyRx3A70oIcBjYPpUB8mAZWY16', 'Admin', 'ROLE_ADMIN'), 3 | ('siva@gmail.com', '$2a$10$CIXGKN9rPfV/mmBMYas.SemoT9mfVUUwUxueFpU3DcWhuNo5fexYC', 'Siva', 'ROLE_MODERATOR') 4 | ; -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/main/resources/db/migration/V3__Add_Bookmarks.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO bookmarks (title, url) VALUES 2 | ('SivaLabs', 'https://sivalabs.in'), 3 | ('Spring Initializr', 'https://start.spring.io') 4 | ; -------------------------------------------------------------------------------- /spring-boot-openapi-swagger/src/test/java/com/sivalabs/myapp/SpringBootOpenapiSwaggerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootOpenapiSwaggerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-rest-api-antipatterns/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot REST API Anti Patterns 2 | 3 | This is a sample repository to demonstrate some of the common anti-patterns in 4 | Spring Boot REST API development. 5 | 6 | * Not following REST API conventions 7 | * No proper URL structure 8 | * Not returning proper HTTP status code 9 | * Using Field Injection 10 | * Not using pagination for collection type resources 11 | * Not using request-specific payload structures (Using entity types as payloads) 12 | * Using JPA DDL auto generation 13 | * Misusing Spring Data JPA derived-query method names 14 | * Loading entire entities and using only a small subset of fields 15 | * Using JPA that results in N+1 Select problems 16 | * Not implementing proper Exception Handling 17 | * Using @SpringBootTest for testing slices (controller, repository) 18 | * Testing with in-memory database 19 | * Not using proper package structure 20 | * package-by-layer 21 | * package-by-feature 22 | * package-by-component 23 | * Hexagonal 24 | * Ports & Adapters 25 | * Clean Code -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/java/com/sivalabs/myapp/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/java/com/sivalabs/myapp/models/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.models; 2 | 3 | import jakarta.validation.constraints.NotEmpty; 4 | 5 | public record LoginRequest(@NotEmpty String email, 6 | @NotEmpty String password) {} -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/java/com/sivalabs/myapp/repositories/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.repositories; 2 | 3 | import com.sivalabs.myapp.entities.Person; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface PersonRepository extends JpaRepository { 9 | Optional findByEmailAndPasswordAndActiveIsTrue(String email, String password); 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/java/com/sivalabs/myapp/services/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.services; 2 | 3 | import com.sivalabs.myapp.entities.Person; 4 | import com.sivalabs.myapp.repositories.PersonRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | @Service 13 | @Transactional 14 | public class PersonService { 15 | @Autowired 16 | private PersonRepository personRepository; 17 | 18 | public List getAllPersons() { 19 | return personRepository.findAll() 20 | .stream().filter(Person::isActive) 21 | .toList(); 22 | } 23 | 24 | public Optional getPersonById(Long id) { 25 | return personRepository.findById(id); 26 | } 27 | 28 | public Person createPerson(Person person) { 29 | return personRepository.save(person); 30 | } 31 | 32 | public Person updatePerson(Person person) { 33 | return personRepository.save(person); 34 | } 35 | 36 | public void deletePerson(Long id) { 37 | personRepository.deleteById(id); 38 | } 39 | 40 | public Optional login(String email, String password) { 41 | return personRepository.findByEmailAndPasswordAndActiveIsTrue(email, password); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/java/com/sivalabs/myapp/web/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.web; 2 | 3 | import com.sivalabs.myapp.entities.Person; 4 | import com.sivalabs.myapp.models.LoginRequest; 5 | import com.sivalabs.myapp.services.PersonService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class LoginController { 16 | 17 | @Autowired 18 | private PersonService personService; 19 | 20 | @PostMapping("/api/login") 21 | public ResponseEntity login( 22 | @RequestBody @Validated LoginRequest loginRequest) { 23 | return personService.login(loginRequest.email(), loginRequest.password()) 24 | .map(ResponseEntity::ok) 25 | .orElse(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-boot-rest-api-antipatterns 2 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/test/java/com/sivalabs/myapp/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootRestApiAntipatternsApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/test/java/com/sivalabs/myapp/repositories/PersonRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.repositories; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | @SpringBootTest 10 | class PersonRepositoryTest { 11 | @Autowired 12 | private PersonRepository personRepository; 13 | 14 | @Test 15 | void shouldGetPersonByIdReturnEmptyWhenNotFound() { 16 | var person = personRepository.findById(1L); 17 | assertThat(person).isEmpty(); 18 | } 19 | } -------------------------------------------------------------------------------- /spring-boot-rest-api-antipatterns/src/test/java/com/sivalabs/myapp/repositories/PersonRepositoryTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.repositories; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | @DataJpaTest 11 | class PersonRepositoryTests { 12 | @Autowired 13 | private PersonRepository personRepository; 14 | 15 | @Test 16 | void shouldGetPersonByIdReturnEmptyWhenNotFound() { 17 | var person = personRepository.findById(1L); 18 | assertThat(person).isEmpty(); 19 | } 20 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | jobs: 8 | build: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup Java 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | cache: 'gradle' 21 | 22 | - name: Build with Gradle 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Maven Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | jobs: 8 | build: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup Java 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | cache: 'maven' 21 | 22 | - name: Build with Maven 23 | run: ./mvnw verify 24 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | build 3 | .gradle 4 | target/ 5 | !.mvn/wrapper/maven-wrapper.jar 6 | !**/src/main/**/target/ 7 | !**/src/test/**/target/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-testcontainers-devmode/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.1/apache-maven-3.9.1-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=17.0.4-tem 2 | gradle=8.1.1 3 | maven=3.9.1 4 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot 3.1.0 Testcontainers Service Connections & Local Development Support Demo 2 | 3 | https://www.youtube.com/watch?v=UuLD9gZmiZU 4 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-testcontainers-devmode/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "spring-boot-testcontainers-devmode" 2 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/Application.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/api/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode.api; 2 | 3 | import com.sivalabs.boottcdevmode.domain.Product; 4 | import com.sivalabs.boottcdevmode.domain.ProductRepository; 5 | import com.sivalabs.boottcdevmode.domain.ProductEventPublisher; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/api/products") 18 | @RequiredArgsConstructor 19 | class ProductController { 20 | private final ProductRepository productRepository; 21 | private final ProductEventPublisher productEventPublisher; 22 | 23 | @GetMapping 24 | public List getAllProducts() { 25 | return productRepository.findAll(); 26 | } 27 | 28 | @PostMapping 29 | public ResponseEntity createProduct(@RequestBody Product product) { 30 | productEventPublisher.publishProductCreatedEvent(product); 31 | return ResponseEntity.ok().build(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode.domain; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.SequenceGenerator; 9 | import jakarta.persistence.Table; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | import lombok.Setter; 14 | 15 | import java.math.BigDecimal; 16 | 17 | @Entity 18 | @Table(name = "products") 19 | @Setter 20 | @Getter 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | public class Product { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_id_generator") 27 | @SequenceGenerator(name = "product_id_generator", sequenceName = "product_id_seq") 28 | private Long id; 29 | 30 | @Column(nullable = false, unique = true) 31 | private String code; 32 | 33 | @Column(nullable = false) 34 | private String name; 35 | 36 | private String description; 37 | 38 | @Column(nullable = false) 39 | private BigDecimal price; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/domain/ProductEventListener.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode.domain; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | @Transactional 12 | @Slf4j 13 | class ProductEventListener { 14 | private final ProductRepository productRepository; 15 | 16 | @KafkaListener(topics = "products") 17 | public void handleProductCreatedEvent(Product product) { 18 | log.info("Product event received from products topic"); 19 | productRepository.save(product); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/domain/ProductEventPublisher.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode.domain; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.core.KafkaTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @RequiredArgsConstructor 10 | @Slf4j 11 | public class ProductEventPublisher { 12 | private final KafkaTemplate kafkaTemplate; 13 | 14 | public void publishProductCreatedEvent(Product product) { 15 | kafkaTemplate.send("products", product); 16 | log.info("ProductCreatedEvent sent to products topic"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/java/com/sivalabs/boottcdevmode/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface ProductRepository extends JpaRepository { 8 | Optional findByCode(String code); 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-boot-testcontainers-devmode 2 | server.port=8080 3 | server.shutdown=graceful 4 | 5 | ######## DB Configuration ######### 6 | #spring.datasource.driver-class-name=org.postgresql.Driver 7 | #spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 8 | #spring.datasource.username=postgres 9 | #spring.datasource.password=postgres 10 | 11 | ######## Kafka Configuration ######### 12 | #KAFKA_BROKER=localhost:9092 13 | #spring.kafka.bootstrap-servers=${KAFKA_BROKER} 14 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 15 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 16 | 17 | spring.kafka.consumer.group-id=tc 18 | spring.kafka.consumer.auto-offset-reset=earliest 19 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 20 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 21 | spring.kafka.consumer.properties.spring.json.trusted.packages=com.sivalabs.* 22 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/resources/db/migration/V1__SchemaInit.sql: -------------------------------------------------------------------------------- 1 | create sequence product_id_seq start with 1 increment by 50; 2 | 3 | create table products 4 | ( 5 | id bigint DEFAULT nextval('product_id_seq') not null, 6 | code varchar(255) not null, 7 | name varchar(255) not null, 8 | description varchar(255), 9 | price numeric(9, 2) not null, 10 | primary key (id), 11 | constraint product_code_unique unique (code) 12 | ); 13 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/main/resources/db/migration/V2__SampleData.sql: -------------------------------------------------------------------------------- 1 | insert into products(code, name, description, price) 2 | values ('P101','Apple MacBook Pro', 'Apple MacBook Pro 2022', 2240.00), 3 | ('P102','Dell Laptop', 'Dell Laptop XPS', 1400.00), 4 | ('P103','Sony TV', 'Sony TV 55 Inches', 740.00) 5 | ; 6 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/test/java/com/sivalabs/boottcdevmode/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 8 | @Import(TestcontainersConfig.class) 9 | class ApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/test/java/com/sivalabs/boottcdevmode/TestApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | 5 | public class TestApplication { 6 | public static void main(String[] args) { 7 | SpringApplication 8 | .from(Application::main) 9 | .with(TestcontainersConfig.class) 10 | .run(args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/test/java/com/sivalabs/boottcdevmode/TestcontainersConfig.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.boottcdevmode; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | import org.testcontainers.containers.KafkaContainer; 7 | import org.testcontainers.containers.PostgreSQLContainer; 8 | import org.testcontainers.utility.DockerImageName; 9 | 10 | @TestConfiguration(proxyBeanMethods = false) 11 | public class TestcontainersConfig { 12 | 13 | @Bean 14 | @ServiceConnection 15 | public PostgreSQLContainer postgreSQLContainer() { 16 | return new PostgreSQLContainer<>("postgres:15.2-alpine"); 17 | } 18 | 19 | @Bean 20 | @ServiceConnection 21 | public KafkaContainer kafkaContainer() { 22 | return new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.2.1")); 23 | } 24 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers-devmode/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-testcontainers/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-testcontainers/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/SpringBootTestcontainersApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootTestcontainersApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootTestcontainersApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | 8 | @Entity 9 | @Table(name = "customers") 10 | @Setter 11 | @Getter 12 | public class Customer { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_id_generator") 16 | @SequenceGenerator(name = "customer_id_generator", sequenceName = "customer_id_seq") 17 | private Long id; 18 | 19 | @Column(nullable = false, unique = true) 20 | private String email; 21 | 22 | @Column(nullable = false) 23 | private String name; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | interface CustomerRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @Transactional 10 | public class CustomerService { 11 | private final CustomerRepository customerRepository; 12 | 13 | public CustomerService(CustomerRepository customerRepository) { 14 | this.customerRepository = customerRepository; 15 | System.out.println("--------CustomerService()---------"); 16 | } 17 | 18 | @Transactional(readOnly = true) 19 | public List getAllCustomers() { 20 | return customerRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/GithubService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | @Service 9 | @Slf4j 10 | public class GithubService { 11 | private final String githubApiBaseUrl; 12 | 13 | public GithubService(@Value("${github.api.base-url}") String githubApiBaseUrl) { 14 | this.githubApiBaseUrl = githubApiBaseUrl; 15 | } 16 | 17 | public String getGithubUserProfile(String username) { 18 | log.info("Github API BaseUrl:" + githubApiBaseUrl); 19 | RestTemplate restTemplate = new RestTemplate(); 20 | return restTemplate.getForObject(githubApiBaseUrl + "/users/" + username, String.class); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import java.math.BigDecimal; 10 | 11 | @Entity 12 | @Table(name = "products") 13 | @Setter 14 | @Getter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class Product { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_id_generator") 21 | @SequenceGenerator(name = "product_id_generator", sequenceName = "product_id_seq") 22 | private Long id; 23 | 24 | @Column(nullable = false, unique = true) 25 | private String name; 26 | 27 | private String description; 28 | 29 | @Column(nullable = false) 30 | private BigDecimal price; 31 | 32 | private boolean disabled; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | 6 | import java.util.List; 7 | 8 | interface ProductRepository extends JpaRepository { 9 | @Query("select p from Product p where p.disabled=false ") 10 | List findAllActiveProducts(); 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/domain/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @Transactional 10 | public class ProductService { 11 | private final ProductRepository productRepository; 12 | 13 | public ProductService(ProductRepository productRepository) { 14 | this.productRepository = productRepository; 15 | System.out.println("--------ProductService()---------"); 16 | } 17 | 18 | @Transactional(readOnly = true) 19 | public List getAllProducts() { 20 | return productRepository.findAll() 21 | .stream().filter(p -> !p.isDisabled()).toList(); 22 | } 23 | 24 | @Transactional(readOnly = true) 25 | public List getActiveProducts() { 26 | return productRepository.findAllActiveProducts(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/rest/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.rest; 2 | 3 | import com.sivalabs.tcdemo.domain.Customer; 4 | import com.sivalabs.tcdemo.domain.CustomerService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | public class CustomerController { 12 | private final CustomerService customerService; 13 | 14 | public CustomerController(CustomerService customerService) { 15 | this.customerService = customerService; 16 | System.out.println("--------CustomerController()---------"); 17 | } 18 | 19 | @GetMapping("/api/customers") 20 | public List getAllCustomers() { 21 | return customerService.getAllCustomers(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/rest/GithubController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.rest; 2 | 3 | import com.sivalabs.tcdemo.domain.GithubService; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("/api/github") 12 | @RequiredArgsConstructor 13 | public class GithubController { 14 | 15 | private final GithubService githubService; 16 | 17 | @GetMapping("/users/{username}") 18 | public String getGithubUserProfile(@PathVariable String username) { 19 | return githubService.getGithubUserProfile(username); 20 | } 21 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/java/com/sivalabs/tcdemo/rest/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.rest; 2 | 3 | import com.sivalabs.tcdemo.domain.Product; 4 | import com.sivalabs.tcdemo.domain.ProductService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | private final ProductService productService; 13 | 14 | public ProductController(ProductService productService) { 15 | this.productService = productService; 16 | System.out.println("--------ProductController()---------"); 17 | } 18 | 19 | @GetMapping("/api/products") 20 | public List getAllProducts() { 21 | return productService.getAllProducts(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.show-sql=true 2 | github.api.base-url=https://api.github.com 3 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/main/resources/db/migration/V1__SchemaInit.sql: -------------------------------------------------------------------------------- 1 | create sequence customer_id_seq start with 1 increment by 50; 2 | create sequence product_id_seq start with 1 increment by 50; 3 | 4 | create table customers 5 | ( 6 | id bigint DEFAULT nextval('customer_id_seq') not null, 7 | email varchar(255) not null, 8 | name varchar(255) not null, 9 | primary key (id) 10 | ); 11 | 12 | create table products 13 | ( 14 | id bigint DEFAULT nextval('product_id_seq') not null, 15 | name varchar(255) not null, 16 | description varchar(255), 17 | price numeric not null, 18 | disabled boolean default false, 19 | primary key (id) 20 | ); -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/domain/ProductServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.mockito.BDDMockito; 6 | import org.mockito.Mockito; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | class ProductServiceTest { 14 | 15 | private ProductRepository productRepository; 16 | private ProductService productService; 17 | 18 | @BeforeEach 19 | void setUp() { 20 | productRepository = Mockito.mock(ProductRepository.class); 21 | productService = new ProductService(productRepository); 22 | } 23 | 24 | @Test 25 | void shouldReturnOnlyActiveProducts() { 26 | //Arrange 27 | Product p1 = new Product(1L, "p-name1", "p-desc1", BigDecimal.TEN, false); 28 | Product p2 = new Product(2L, "p-name2", "p-desc2", BigDecimal.TEN, true); 29 | BDDMockito.given(productRepository.findAll()).willReturn(List.of(p1, p2)); 30 | 31 | 32 | //Act 33 | List products = productService.getAllProducts(); 34 | 35 | //Assert 36 | assertThat(products).hasSize(1); 37 | assertThat(products.get(0).getId()).isEqualTo(1L); 38 | } 39 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/domain/ProductServiceWithMockitoExtensionTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.domain; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.BDDMockito; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | class ProductServiceWithMockitoExtensionTest { 17 | 18 | @Mock 19 | private ProductRepository productRepository; 20 | 21 | @InjectMocks 22 | private ProductService productService; 23 | 24 | @Test 25 | void shouldReturnOnlyActiveProducts() { 26 | //Arrange 27 | Product p1 = new Product(1L, "p-name1", "p-desc1", BigDecimal.TEN, false); 28 | Product p2 = new Product(2L, "p-name2", "p-desc2", BigDecimal.TEN, true); 29 | BDDMockito.given(productRepository.findAll()).willReturn(List.of(p1, p2)); 30 | 31 | //Act 32 | List products = productService.getAllProducts(); 33 | 34 | //Assert 35 | assertThat(products).hasSize(1); 36 | assertThat(products.get(0).getId()).isEqualTo(1L); 37 | } 38 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/infra/MockServerContainerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.infra; 2 | 3 | import org.springframework.boot.test.util.TestPropertyValues; 4 | import org.springframework.context.ApplicationContextInitializer; 5 | import org.springframework.context.ConfigurableApplicationContext; 6 | import org.testcontainers.containers.MockServerContainer; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | public class MockServerContainerInitializer implements ApplicationContextInitializer { 10 | public static MockServerContainer mockServerContainer = 11 | new MockServerContainer(DockerImageName.parse("jamesdbloom/mockserver:mockserver-5.13.2")); 12 | 13 | static { 14 | mockServerContainer.start(); 15 | } 16 | 17 | @Override 18 | public void initialize(ConfigurableApplicationContext configurableApplicationContext) { 19 | TestPropertyValues.of("github.api.base-url=" + mockServerContainer.getEndpoint() 20 | ).applyTo(configurableApplicationContext.getEnvironment()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/infra/PostgresDatabaseContainerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.infra; 2 | 3 | import org.springframework.boot.test.util.TestPropertyValues; 4 | import org.springframework.context.ApplicationContextInitializer; 5 | import org.springframework.context.ConfigurableApplicationContext; 6 | import org.testcontainers.containers.PostgreSQLContainer; 7 | 8 | public class PostgresDatabaseContainerInitializer implements ApplicationContextInitializer { 9 | private static final PostgreSQLContainer sqlContainer = 10 | new PostgreSQLContainer<>("postgres:14-alpine") 11 | .withDatabaseName("integration-tests-db") 12 | .withUsername("sa") 13 | .withPassword("sa") 14 | .withReuse(true); 15 | 16 | static { 17 | sqlContainer.start(); 18 | } 19 | 20 | public void initialize (ConfigurableApplicationContext configurableApplicationContext){ 21 | TestPropertyValues.of( 22 | "spring.datasource.url=" + sqlContainer.getJdbcUrl(), 23 | "spring.datasource.username=" + sqlContainer.getUsername(), 24 | "spring.datasource.password=" + sqlContainer.getPassword() 25 | ).applyTo(configurableApplicationContext.getEnvironment()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/rest/ProductControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.rest; 2 | 3 | import com.sivalabs.tcdemo.infra.MockServerContainerInitializer; 4 | import com.sivalabs.tcdemo.infra.PostgresDatabaseContainerInitializer; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 14 | 15 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 16 | @AutoConfigureMockMvc 17 | @ContextConfiguration(initializers = {PostgresDatabaseContainerInitializer.class}) 18 | class ProductControllerIntegrationTest { 19 | 20 | @Autowired 21 | private MockMvc mockMvc; 22 | 23 | @Test 24 | void shouldReturnActiveProducts() throws Exception { 25 | mockMvc.perform(get("/api/products")) 26 | .andExpect(status().isOk()); 27 | } 28 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/java/com/sivalabs/tcdemo/rest/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.tcdemo.rest; 2 | 3 | import com.sivalabs.tcdemo.domain.ProductService; 4 | import org.junit.jupiter.api.Test; 5 | import org.mockito.BDDMockito; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.List; 12 | 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | @WebMvcTest(controllers = ProductController.class) 17 | class ProductControllerTest { 18 | 19 | @MockBean 20 | private ProductService productService; 21 | 22 | @Autowired 23 | private MockMvc mockMvc; 24 | 25 | @Test 26 | void shouldReturnActiveProducts() throws Exception { 27 | BDDMockito.given(productService.getAllProducts()).willReturn(List.of()); 28 | 29 | mockMvc.perform(get("/api/products")) 30 | .andExpect(status().isOk()); 31 | } 32 | } -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spring-boot-testcontainers/src/test/resources/testcontainers.properties: -------------------------------------------------------------------------------- 1 | #dockerconfig.source=autoIgnoringUserProperties 2 | #testcontainers.reuse.enable=true 3 | -------------------------------------------------------------------------------- /spring-boot-testing-types/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-boot-testing-types/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivaprasadreddy/sivalabs-youtube-code-samples/93630c946d0115a7728c4be190a220e2c5e0cb12/spring-boot-testing-types/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-boot-testing-types/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/SpringBootTestingTypesApplication.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootTestingTypesApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootTestingTypesApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | 8 | @Entity 9 | @Table(name = "customers") 10 | @Setter 11 | @Getter 12 | public class Customer { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_id_generator") 16 | @SequenceGenerator(name = "customer_id_generator", sequenceName = "customer_id_seq") 17 | private Long id; 18 | 19 | @Column(nullable = false, unique = true) 20 | private String email; 21 | 22 | @Column(nullable = false) 23 | private String name; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | interface CustomerRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @Transactional 10 | public class CustomerService { 11 | private final CustomerRepository customerRepository; 12 | 13 | public CustomerService(CustomerRepository customerRepository) { 14 | this.customerRepository = customerRepository; 15 | System.out.println("--------CustomerService()---------"); 16 | } 17 | 18 | @Transactional(readOnly = true) 19 | public List getAllCustomers() { 20 | return customerRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/Product.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import java.math.BigDecimal; 10 | 11 | @Entity 12 | @Table(name = "products") 13 | @Setter 14 | @Getter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class Product { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_id_generator") 21 | @SequenceGenerator(name = "product_id_generator", sequenceName = "product_id_seq") 22 | private Long id; 23 | 24 | @Column(nullable = false, unique = true) 25 | private String name; 26 | 27 | private String description; 28 | 29 | @Column(nullable = false) 30 | private BigDecimal price; 31 | 32 | private boolean disabled; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | 6 | import java.util.List; 7 | 8 | interface ProductRepository extends JpaRepository { 9 | @Query("select p from Product p where p.disabled=false ") 10 | List findAllActiveProducts(); 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/domain/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | import java.util.List; 7 | 8 | @Service 9 | @Transactional 10 | public class ProductService { 11 | private final ProductRepository productRepository; 12 | 13 | public ProductService(ProductRepository productRepository) { 14 | this.productRepository = productRepository; 15 | System.out.println("--------ProductService()---------"); 16 | } 17 | 18 | @Transactional(readOnly = true) 19 | public List getAllProducts() { 20 | return productRepository.findAll() 21 | .stream().filter(p -> !p.isDisabled()).toList(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/rest/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.rest; 2 | 3 | import com.sivalabs.myapp.domain.Customer; 4 | import com.sivalabs.myapp.domain.CustomerService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | public class CustomerController { 12 | private final CustomerService customerService; 13 | 14 | public CustomerController(CustomerService customerService) { 15 | this.customerService = customerService; 16 | System.out.println("--------CustomerController()---------"); 17 | } 18 | 19 | @GetMapping("/api/customers") 20 | public List getAllCustomers() { 21 | return customerService.getAllCustomers(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/java/com/sivalabs/myapp/rest/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.rest; 2 | 3 | import com.sivalabs.myapp.domain.Product; 4 | import com.sivalabs.myapp.domain.ProductService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | public class ProductController { 12 | private final ProductService productService; 13 | 14 | public ProductController(ProductService productService) { 15 | this.productService = productService; 16 | System.out.println("--------ProductController()---------"); 17 | } 18 | 19 | @GetMapping("/api/products") 20 | public List getAllProducts() { 21 | return productService.getAllProducts(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.show-sql=true 2 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/main/resources/db/migration/V1__SchemaInit.sql: -------------------------------------------------------------------------------- 1 | create sequence customer_id_seq start with 1 increment by 50; 2 | create sequence product_id_seq start with 1 increment by 50; 3 | 4 | create table customers 5 | ( 6 | id bigint DEFAULT nextval('customer_id_seq') not null, 7 | email varchar(255) not null, 8 | name varchar(255) not null, 9 | primary key (id) 10 | ); 11 | 12 | create table products 13 | ( 14 | id bigint DEFAULT nextval('product_id_seq') not null, 15 | name varchar(255) not null, 16 | description varchar(255), 17 | price numeric not null, 18 | disabled boolean default false, 19 | primary key (id) 20 | ); -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/SpringBootTestingTypesApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootTestingTypesApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/domain/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 6 | 7 | import javax.persistence.EntityManager; 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @DataJpaTest 14 | class ProductRepositoryTest { 15 | 16 | @Autowired 17 | private ProductRepository productRepository; 18 | 19 | @Autowired 20 | private EntityManager entityManager; 21 | 22 | @Test 23 | void shouldGetAllActiveProducts() { 24 | entityManager.persist(new Product(null, "pname1", "pdescr1", BigDecimal.TEN, false)); 25 | entityManager.persist(new Product(null, "pname2", "pdescr2", BigDecimal.TEN, true)); 26 | 27 | List products = productRepository.findAllActiveProducts(); 28 | 29 | assertThat(products).hasSize(1); 30 | assertThat(products.get(0).getName()).isEqualTo("pname1"); 31 | } 32 | } -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/domain/ProductServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.mockito.BDDMockito; 6 | import org.mockito.Mockito; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | class ProductServiceTest { 14 | 15 | private ProductRepository productRepository; 16 | private ProductService productService; 17 | 18 | @BeforeEach 19 | void setUp() { 20 | productRepository = Mockito.mock(ProductRepository.class); 21 | productService = new ProductService(productRepository); 22 | } 23 | 24 | @Test 25 | void shouldReturnOnlyActiveProducts() { 26 | //Arrange 27 | Product p1 = new Product(1L, "p-name1", "p-desc1", BigDecimal.TEN, false); 28 | Product p2 = new Product(2L, "p-name2", "p-desc2", BigDecimal.TEN, true); 29 | BDDMockito.given(productRepository.findAll()).willReturn(List.of(p1, p2)); 30 | 31 | //Act 32 | List products = productService.getAllProducts(); 33 | 34 | //Assert 35 | assertThat(products).hasSize(1); 36 | assertThat(products.get(0).getId()).isEqualTo(1L); 37 | } 38 | } -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/domain/ProductServiceWithMockitoExtensionTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.domain; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.BDDMockito; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | class ProductServiceWithMockitoExtensionTest { 17 | 18 | @Mock 19 | private ProductRepository productRepository; 20 | 21 | @InjectMocks 22 | private ProductService productService; 23 | 24 | @Test 25 | void shouldReturnOnlyActiveProducts() { 26 | //Arrange 27 | Product p1 = new Product(1L, "p-name1", "p-desc1", BigDecimal.TEN, false); 28 | Product p2 = new Product(2L, "p-name2", "p-desc2", BigDecimal.TEN, true); 29 | BDDMockito.given(productRepository.findAll()).willReturn(List.of(p1, p2)); 30 | 31 | //Act 32 | List products = productService.getAllProducts(); 33 | 34 | //Assert 35 | assertThat(products).hasSize(1); 36 | assertThat(products.get(0).getId()).isEqualTo(1L); 37 | } 38 | } -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/rest/ProductControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.rest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.web.servlet.MockMvc; 8 | 9 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 | 12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 13 | @AutoConfigureMockMvc 14 | class ProductControllerIntegrationTest { 15 | 16 | @Autowired 17 | private MockMvc mockMvc; 18 | 19 | @Test 20 | void shouldReturnActiveProducts() throws Exception { 21 | mockMvc.perform(get("/api/products")) 22 | .andExpect(status().isOk()); 23 | } 24 | } -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/java/com/sivalabs/myapp/rest/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.sivalabs.myapp.rest; 2 | 3 | import com.sivalabs.myapp.domain.ProductService; 4 | import org.junit.jupiter.api.Test; 5 | import org.mockito.BDDMockito; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.List; 12 | 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | @WebMvcTest(controllers = ProductController.class) 17 | class ProductControllerTest { 18 | 19 | @MockBean 20 | private ProductService productService; 21 | 22 | @Autowired 23 | private MockMvc mockMvc; 24 | 25 | @Test 26 | void shouldReturnActiveProducts() throws Exception { 27 | BDDMockito.given(productService.getAllProducts()).willReturn(List.of()); 28 | 29 | mockMvc.perform(get("/api/products")) 30 | .andExpect(status().isOk()); 31 | } 32 | } -------------------------------------------------------------------------------- /spring-boot-testing-types/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------