{"id":36876303,"url":"https://github.com/terry960302/sample-spring-webflux-pattern","last_synced_at":"2026-01-12T15:10:07.520Z","repository":{"id":127029509,"uuid":"541427664","full_name":"terry960302/sample-spring-webflux-pattern","owner":"terry960302","description":"(Kotlin, Postgresql, Webflux, Springboot, Coroutine etc...) 저처럼 인터넷 휘젓고 다니지 말고 webflux 패턴으로 spring으로 시작하시려는 분에게 도움을 주고자 만들었습니다.","archived":false,"fork":false,"pushed_at":"2025-07-23T04:44:47.000Z","size":357,"stargazers_count":128,"open_issues_count":0,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-23T06:23:53.841Z","etag":null,"topics":["cloud-storage","coroutine","docker","gcp","gradle","hexagonal-architecture","image-upload","kotlin","mockk","multistage-build","oop","postgresql","r2dbc","spring","spring-boot","spring-webflux","webflux","webflux-security"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/terry960302.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-09-26T05:50:08.000Z","updated_at":"2025-07-23T04:44:50.000Z","dependencies_parsed_at":"2025-06-24T16:24:57.439Z","dependency_job_id":"b10bf094-8c9e-41bd-8244-df74f82206c0","html_url":"https://github.com/terry960302/sample-spring-webflux-pattern","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/terry960302/sample-spring-webflux-pattern","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terry960302%2Fsample-spring-webflux-pattern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terry960302%2Fsample-spring-webflux-pattern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terry960302%2Fsample-spring-webflux-pattern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terry960302%2Fsample-spring-webflux-pattern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/terry960302","download_url":"https://codeload.github.com/terry960302/sample-spring-webflux-pattern/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terry960302%2Fsample-spring-webflux-pattern/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28340419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloud-storage","coroutine","docker","gcp","gradle","hexagonal-architecture","image-upload","kotlin","mockk","multistage-build","oop","postgresql","r2dbc","spring","spring-boot","spring-webflux","webflux","webflux-security"],"created_at":"2026-01-12T15:10:06.771Z","updated_at":"2026-01-12T15:10:07.514Z","avatar_url":"https://github.com/terry960302.png","language":"Kotlin","readme":"\u003cdiv align=\"right\"\u003e\n  \u003cb\u003eContributor : Taewan Kim(100%)\u003c/b\u003e\n\u003c/div\u003e\n\n# Sample Spring Webflux Reactive Pattern Template\n\u003e 간단한 커뮤니티 구조의 백엔드 API 서버를 Hexagonal Architecture 에 기반하여 구현했습니다.(Webflux패턴 기반)\n\u003cimg width=\"1536\" height=\"1024\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/1a11e0fb-5691-4b36-81ea-c3988754b2c5\" /\u003e\n\u003e\u003e Simple Social network stuff Structure based Back-end API server using Spring Webflux pattern.\n\n## Background\n- 개인 포트폴리오가 전부 상업용 프로젝트이기도 하고 백엔드 관련한 개인적인 repo가 충분하지 않았다.\n- 개인적으로 만들고 있는 프로젝트(InteractiveERD)에서 백엔드를 뭘로 할지 고민을 하고 있었다.\n- 지인중 한 분이 본인 팀에서 Spring Webflux를 사용한다고 하길래 트랜드인가 싶어 익숙한 kotlin으로 공부해보면 좋겠다는 생각이 들었다.(옷은 트랜드를 안 따라가더라도 프로그래밍은 못참지)\n- reactive 패턴은 이전에 rxdart로 질리도록 해봐서 재밌을거라 생각했다.\n\n## Issues\n: Found out some issues during making this project. \n\n### About Webflux `Reactor pattern`\n1. Reactor pattern(using Mono, Flux) can reduce code readability.(callback hell like  before async/await pattern popped out in Node.js) \n  - Resolved by using `kotlin coroutine`.(persist non-blocking)\n\n### About `R2DBC`\n1. It's not an ORM(need to write raw query and mapper in some cases)\n2. Recommend to use R2DBC because of blocking issue when using JDBC(it's blocking API) instead.\n   - Could not take advantage of confidence when using JPA.\n3. R2DBC does not support relation mapping like ORM (No plan for it)\n     ![image](https://user-images.githubusercontent.com/37768791/193259695-01a3ad7f-86e3-4dd5-a6da-7e892c36a013.png)\n4. Performance issues when using Postgresql driver compared to JDBC\n   - ref : https://github.com/spring-projects/spring-data-r2dbc/issues/203\n     ![image](https://user-images.githubusercontent.com/37768791/193275900-56b36a90-3b84-4b6b-b949-4cbb22c508dc.png)\n5. ~~Pagination with sort and offset need to use databaseClient(raw query). It won't work with @Query annotation.~~\n   - ref: https://github.com/spring-projects/spring-data-r2dbc/issues/596\n     ![image](https://user-images.githubusercontent.com/37768791/193278481-6bdd70a1-70d8-440b-b256-cc546c12d19f.png)\n   - Resolved by Fluent API in R2DBC.(can put sort or offset stuffs using Fluent API template.)\n6. There's other relation mapping support(alternatives) on reactive db connection API which called `Hibernate Reactive`(can use with Hibernate ORM) and `Kotlin JDSL Reactive`\n7. [221223 UPDATE] QueryDsl could resolve relation mapping issue\n8. [221223 UPDATE] What about [java-dataloader](https://github.com/graphql-java/java-dataloader) to resolve `n+1` issue when want to fetch join data not using join functionality.\n\n\n# Introduction\n\n### ERD \n![image](https://user-images.githubusercontent.com/37768791/192972062-0a38529b-c326-4b6f-9917-4b6a547b481c.png)\n\n### Tech Stacks\n- `Kotlin` 언어 사용(이전에 안드로이드 개발을 kotlin으로도 했고 꽤나 열심히 언어공부를 했던 언어이기에 선택함.)\n- `gradle` 빌드 방식 적용(안드로이드에 익숙함)\n- `Webflux` 패턴 채택\n- `함수형(Functional)` 프로그래밍 적용\n- `Postgresql` driver 사용(원래는 mysql이 익숙한데 tokenizing때문에 postgresql을 공부하기로 맘먹음)\n- 논블로킹을 위해 JPA가 아닌 `R2DBC` 채택 (ORM이 아니라서 고민중)\n- 클라우드는 `GCP`를 사용 for file upload\n\n## Project Goals\n\u003e R2DBC의 제약사항인 JOIN 연산의 복잡성을 해결하면서도 완전한 논블로킹 처리를 구현하는 것이 주요 목표입니다.\n\n## What did I solve\n- [x] N+1 문제 해결 - 연관관계 데이터를 효율적으로 로딩\n- [x] Reactor Pattern 이 아닌 Kotlin Coroutine 구조로 가독성을 올림\n- [x] 논블로킹 JOIN - R2DBC 환경에서 복잡한 연관관계 처리\n- [x] 성능 최적화 - 300-500ms 응답시간을 개선\n- [x] 확장 가능한 아키텍처 - Hexagonal Architecture 적용\n\n## Features \n\n#### 인증 \u0026 사용자 관리\n- [x] 회원가입/로그인 - JWT 토큰 기반 인증\n- [x] 비밀번호 암호화 - BCrypt 해싱\n- [x] 사용자 프로필 관리 - 정보 수정, 프로필 이미지\n- [x] 권한 기반 접근 제어 - Role 기반 권한 체계\n\n#### 게시물 \u0026 댓글\n- [x] 게시물 CRUD - 생성, 조회, 수정, 삭제\n- [x] 댓글 시스템 - 게시물별 댓글 관리\n- [x] 이미지 첨부 - 게시물에 다중 이미지 첨부\n- [x] 연관관계 조회 - 게시물-사용자-이미지 논블로킹 JOIN\n\n#### 이미지 처리\n- [x] 단일/다중 이미지 업로드 - 병렬 처리로 성능 최적화\n- [x] 이미지 검증 - 파일 크기, 형식, 해상도 검증\n- [x] 웹 최적화 체크 - 이미지 최적화 권장 로직\n- [x] GCP Storage 연동 - 클라우드 스토리지 활용\n\n#### 논블로킹 JOIN 구현\n- [x] User-Posting 연관관계 - 사용자별 게시물 조회\n- [x] Posting-Comment 연관관계 - 게시물별 댓글 조회\n- [x] Posting-Image 연관관계 - 게시물별 이미지 조회\n- [x] 복합 연관관계 - 한 번에 모든 연관 데이터 로딩\n\n# Get Started!\n\n### Environment(yml) Setup\n\n```yml\n# src/main/resources/application.yml\nserver:\n  host: localhost\n  port: 8080\n\nspring:\n  application:\n    name: spring-webflux-template\n\n  r2dbc:\n    protocol: r2dbc:postgresql\n    host: localhost\n    port: 5432\n    username: postgres\n    password: postgres\n    database: postgres\n\n  sql:\n    init:\n      mode: always # 이걸 설정하면 스키마가 업로드됩니다.\n      continue-on-error: false\n\napp:\n  security:\n    jwt:\n      secret: \u003cSECRET_KEY_STR\u003e \n      expiration: \u003cTOKEN_EXPIRATION_LONG\u003e\n    password:\n      hash:\n        rounds: \u003cHASH_ROUNDS_INT\u003e\n\n  storage:\n    cred-path: \u003cGCP_CREDENTIAL_JSON_PATH\u003e \n    project-id: \u003cGCP_PROJECT_ID\u003e\n    bucket-name: \u003cGCP_STORAGE_BUCKET_NAME\u003e\n\nlogging:\n  level:\n    org.springframework.r2dbc: DEBUG\n    io.r2dbc: DEBUG\n\n```\n### Profile Setup \u0026 Run\n```bash\n# Itellij Configuration \u003e VM option \n-Dspring.profiles.active=\u003cYOUR PROFILE\u003e\n\n# Use Command line to run build jar file\njava -jar -Dspring.profiles.active=\u003cYOUR PROFILE\u003e path/to/app.jar\n```\n\n## References\n\u003e 공부하면서 참고한 링크는 아래에 모두 기록할 예정입니다. 프로젝트 코드보다 개념이 필요하신분들은 아래 링크들에 잘 기록되어있을테니 참고하시면 되겠습니다.(주의 : 개념이 없는 링크들도 있을수도 있음.)\n\n(Warning: it's almost Korean blog links. )\n\n#### R2DBC 사용법 파악 및 아키텍처 파악(R2DBC use-cases and architectures)\n- URL : https://github.com/piomin/sample-spring-data-webflux\n- URL : https://docs.spring.io/spring-data/r2dbc/docs/current-SNAPSHOT/reference/html/#reference\n- URL : https://www.vinsguru.com/spring-data-r2dbc/\n- URL : https://github.com/hantsy/spring-r2dbc-sample\n- URL : https://medium.com/bliblidotcom-techblog/reactive-spring-boot-application-with-r2dbc-and-postgresql-849fc7811135\n- URL : https://github.com/kakaohairshop/spring-r2dbc-study\n\n#### application.yaml 사용법 (application.yaml use-cases)\n- URL : https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html\n\n#### profile별 빌드 및 실행방법 (How to set up profile for building and running spring boot project)\n- URL : https://1minute-before6pm.tistory.com/12\n\n#### Route 코드 리팩토링 (RouteConfig refactoring)\n- URL : https://github.com/spring-projects/spring-framework/issues/28603\n\n#### Webflux 에러 핸들링 (Webflux server error handling conventions)\n- URL : https://www.baeldung.com/spring-webflux-errors\n\n#### Webflux 폴더구조 참고용 (Webflux projects directory structure)\n- URL : https://github.com/kamalhm/spring-boot-r2dbc/tree/master/src/main/java/com/khm/reactivepostgres\n\n#### R2DBC에서 관계 테이블 구현 (Implements relations when using R2DBC)\n(공식적으로 r2dbc는 관계relation를 지원하지 않는다.)\n\n지원하고 있지 않다는 이슈\n- URL : https://github.com/spring-projects/spring-data-r2dbc/issues/99\n- URL : https://github.com/spring-projects/spring-data-r2dbc/issues/352\n\n관계 매핑 구현방법\n- URL : https://tolkiana.com/introduction-to-spring-data-r2dbc-with-kotlin/\n- URL : https://heesutory.tistory.com/33\n- URL : https://heesutory.tistory.com/35\n  - Lombok의 @Builder 대신 kotlin으로 해결(URL : https://levelup.gitconnected.com/kotlin-makes-lombok-obsolete-9ed3318596cb, https://stackoverflow.com/questions/36140791/how-to-implement-builder-pattern-in-kotlin)\n- URL : https://binux.tistory.com/156\n- URL : https://medium.com/pictet-technologies-blog/reactive-programming-with-spring-data-r2dbc-ee9f1c24848b\n  - Combinations of repo query to make join relations \n- URL : https://www.sipios.com/blog-tech/handle-the-new-r2dbc-specification-in-java\n  - Raw query join and aggregate cause blocking issue\n\n#### 코루틴 적용하는 법(코루틴 도입배경) (Apply Kotlin coroutine in spring server)\n- URL : https://techblog.woowahan.com/7349/\n- URL : https://appleg1226.tistory.com/16\n- URL(coRouter 구현) : https://veluxer62.github.io/tutorials/spring-web-flux-functional-endpoint-api/\n- URL : https://umbum.dev/1047\n\n#### Repository DatabaseClient Transaction 적용 \n- URL : https://spring.io/blog/2019/05/16/reactive-transactions-with-spring\n\n#### Logger Setup\n- URL : https://jsonobject.tistory.com/500\n- URL : https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/\n\n#### R2DBC vs Hibernate Reactive\n- URL : https://medium.com/geekculture/spring-data-jpa-spring-data-r2dbc-hibernate-reactive-bcc43e321566\n\n#### Hibernate Reactive 사용법\n- URL : https://hantsy.github.io/spring-puzzles/hibernate-reactive.html\n- URL : https://hibernate.org/reactive/\n- URL : https://itnext.io/integrating-hibernate-reactive-with-spring-5427440607fe\n\n#### kotest examples(with Webflux)\n: official docs가 아주 잘 되어있음.\n- URL : https://github.com/kotest/kotest-examples-spring-webflux\n- URL : https://velog.io/@revimal/Kotlin-Kotest%EC%99%80-MockK%EB%A1%9C-Spring-Boot%EC%97%90%EC%84%9C-Kotlin-%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1\n- URL : https://github.com/HomoEfficio/dev-tips/blob/master/Kotlin-Coroutine-Spring-WebFluxTest.md\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterry960302%2Fsample-spring-webflux-pattern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fterry960302%2Fsample-spring-webflux-pattern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterry960302%2Fsample-spring-webflux-pattern/lists"}