{"id":25122951,"url":"https://github.com/f-lab-edu/jshop","last_synced_at":"2025-07-01T04:34:58.508Z","repository":{"id":242524999,"uuid":"808111714","full_name":"f-lab-edu/jshop","owner":"f-lab-edu","description":"이커머스 웹서비스를 만들어보고 성능 테스트를 진행해 병목을 제거해 성능을 최적화해보는 프로젝트","archived":false,"fork":false,"pushed_at":"2024-09-11T08:36:17.000Z","size":8403,"stargazers_count":10,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-23T01:12:25.059Z","etag":null,"topics":["grafana","java","mysql8","ngrinder","redis","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","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/f-lab-edu.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":"2024-05-30T12:07:14.000Z","updated_at":"2025-04-04T04:05:43.000Z","dependencies_parsed_at":"2024-07-08T16:21:33.104Z","dependency_job_id":"0708a964-a097-4741-b52c-6d1bf01dfa05","html_url":"https://github.com/f-lab-edu/jshop","commit_stats":null,"previous_names":["f-lab-edu/jshop"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/f-lab-edu/jshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-lab-edu%2Fjshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-lab-edu%2Fjshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-lab-edu%2Fjshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-lab-edu%2Fjshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/f-lab-edu","download_url":"https://codeload.github.com/f-lab-edu/jshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-lab-edu%2Fjshop/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262898703,"owners_count":23381611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["grafana","java","mysql8","ngrinder","redis","spring-boot"],"created_at":"2025-02-08T07:15:41.024Z","updated_at":"2025-07-01T04:34:58.483Z","avatar_url":"https://github.com/f-lab-edu.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSHOP\n\n![architecture](/images/architecture.png)\n\n## 프로젝트 목표\n\n* 이커머스 플랫폼을 설계해보고 직접 구현합니다.\n* 테스트 데이터를 넣어 성능 테스트를 진행하고 결과를 분석해 최적화 합니다.\n* https://github.com/f-lab-edu/jshop/wiki/성능-테스트\n\n## 프로젝트 이슈사항\n\n1. 검색 성능 최적화 과정 (`0.5 RPS` -\u003e `45 RPS`, `4000ms` -\u003e `200ms`)    \n   https://velog.io/@jhkim31/series/검색-성능-최적화\n\n\n2. 로그인 성능 최적화 과정 (`60RPS` -\u003e `450 RPS`)    \n   https://velog.io/@jhkim31/로그인-성능-최적화\n\n\n3. 자원 인가로직 **AoP**를 사용해 분리    \n   https://velog.io/@jhkim31/리팩토링-1.-중복-인가로직-제거    \n   https://velog.io/@jhkim31/리팩토링-2.-서비스-레이어-PreAuthorize-사용\n\n\n4. 테스트에서 `@Transactional` 사용에 관한 문제    \n   https://velog.io/@jhkim31/테스트와-Transactional\n\n\n5. **KVM**, **Docker Swarm** 테스트 인프라 구축    \n   https://velog.io/@jhkim31/series/테스트-인프라-구축\n\n\n6. 데이터 베이스 재고 **동시성 문제** 해결   \n   https://velog.io/@jhkim31/데이터베이스-동시성-문제\n\n\n7. `@Transcational`과 분산락 사용시 고려해야 할 점    \n   https://velog.io/@jhkim31/트러블슈팅-분산락과-Transactional\n\n\n8. 테스트 의존 해결을 위한 **테스트 컨테이너** 도입   \n   https://velog.io/@jhkim31/Spring-테스트-컨테이너\n\n\n9. `WebMvcTest` 슬라이스 테스트에서 `SecurityContext` 설정     \n   https://velog.io/@jhkim31/Controller-단위-테스트에서-SecurityContext를-설정해보자\n\n\n10. 테스트 데이터 삽입 최적화  \n    https://velog.io/@jhkim31/테스트-데이터-삽입-최적화\n\n\n11. **스프링 MVC** 로깅하기   \n    https://velog.io/@jhkim31/스프링-MVC-로깅하기\n\n## 프로젝트 중점사항\n\n* **객체지향 설계**를 통해 재사용성과 유지보수성을 높임\n* 비즈니스로직을 최대한 엔티티에 작성해 재사용성과 테스트 용이성을 높임\n\n\n* **git-flow** 정책과 `rebase, cherry-pick` 등 명령으로 브랜치를 용도별로 깔끔하게 관리\n* Github의 **Wiki**, **Projects** 기능을 활용해 프로젝트 관리\n* 원활한 협업(**PR**, **코드리뷰**)를 위한 커밋 작성\n\n\n* **자원에 대한 인가**, **분산락**등 수평 관심사를 **AoP**로 분리\n* **스프링 시큐리티**를 사용해 JWT 인증, 인가를 구현\n* 시큐리티 프록시 필터 앞에 로깅 필터를 넣어 **모든 요청 로그를 기록**\n* 테스트 환경에서 시큐리티 컨텍스트를 제공\n* 개발과 배포 환경을 **프로파일로 분리**하여 각 환경에 맞는 배포 파이프라인을 구축\n* **멀티모듈** 구조를 적용해 스프링 컨텍스트에 대한 학습과 모듈 단위로 코드의 재사용성을 높임\n\n\n* **JPA**를 사용해 데이터 접근을 추상화\n* **엔티티**를 직접 설계해보며 성능과 관련된 문제들을 해결\n* **QueryDSL**을 사용해 검색 동적 쿼리 해결\n* **Cursor**, **Offset** 을 사용한 페이징 구현\n\n\n* 단위 테스트를 각 계층별로 **슬라이스 테스트**로 작성해 빠르고 반복적인 테스트 수행\n* 통합 테스트시 외부 의존을 해결하기 위해 모든 테스트에서 하나의 **테스트 컨테이너**를 싱글톤으로 사용\n* 성능 테스트를 위한 인프라와 모니터링 환경을 구축\n* 성능 테스트를 통해 시스템의 병목지점을 파악해 해결\n* MySQL **실행계획분석**과 **인덱스**를 통해 쿼리를 최적화\n\n\n* **KVM** 과 **Docker Swarm** 을 사용해 인프라 구축 (3 마스터, 2 워커)\n* 특정 Github 이벤트 발생시 웹훅으로 **Jenkins**를 통해 서버에 배포까지 이어지는 파이프라인을 구축\n* **Prometheus**로 데이터를 수집하고, **Grafana**로 시각화\n* **ELK** 스택을 구축해 수평확장이 되는 환경에서도 로그를 쉽게 수집, 확인\n\n## API\n\n### User\n\n| METHOD  | URI                  | 설명                                       |\n|:--------|:---------------------|:-----------------------------------------|\n| `POST`  | `/api/login`         | 로그인 (Spring Security)                    |\n| `POST`  | `/api/logout`        | 로그아웃 (Spring Security)                   |\n| `POST`  | `/api/join`          | 회원가입                                     |\n| `GET`   | `/api/users`         | 회원 정보 가져오기, JWT로 회원 구분 (기본정보, 잔액, 주소정보등) |\n| `PATCH` | `/api/users`         | 회원 정보 수정                                 |\n| `PATCH` | `/api/users/balance` | 회원 잔액 수정                                 |\n| `PATCH` | `/api/users/balance` | 회원 잔액 수정                                 |\n\n### Address\n\n| METHOD   | URI                   | 설명                  |\n|:---------|:----------------------|:--------------------|\n| `POST`   | `/api/addresses`      | 현재 로그인한 회원으로 주소 생성  |\n| `DELETE` | `/api/addresses/{id}` | 회원이 주소의 소유자라면 주소 삭제 |\n| `PUT`    | `/api/addresses/{id}` | 회원이 주소의 소유자라면 주소 수정 |\n\n### Cart\n\n| METHOD   | URI                                      | 설명                          |\n|:---------|:-----------------------------------------|:----------------------------|\n| `POST`   | `/api/cart`                              | 현재 로그인한 회원의 장바구니에 상품 추가     |\n| `GET`    | `/api/cart?page={page:0}\u0026size={size:30}` | 현재 로그인한 회원의 장바구니 가져오기       |\n| `PUT`    | `/api/cart/{cart_product_detail_id}`     | 현재 로그인한 회원의 장바구니 상품 수정 (수량) |\n| `DELETE` | `/api/cart/{cart_product_detail_id}`     | 현재 로그인한 회원의 장바구니 상품 삭제      |\n\n### Category\n\n| METHOD | URI               | 설명                 |\n|:-------|:------------------|:-------------------|\n| `POST` | `/api/categories` | 카테고리 생성 (ADMIN 전용) |\n| `GET`  | `/api/categories` | 카테고리 정보 가져오기       |\n\n### Coupon\n\n| METHOD | URI                       | 설명               |\n|:-------|:--------------------------|:-----------------|\n| `POST` | `/api/coupon`             | 쿠폰 생성 (ADMIN 전용) |\n| `POST` | `/api/coupon/{coupon_id}` | 쿠폰 발급 요청 (일반 회원) |\n\n### Search\n\n| METHOD | URI                                                                | 설명                                   |\n|:-------|:-------------------------------------------------------------------|:-------------------------------------|\n| `GET`  | `/api/search?query={query}`                                        | 상품 검색 (query)                        |\n| `GET`  | `/api/search?manufacturer={manufacturer}`                          | 상품 검색 필터링 (제조사)                      |\n| `GET`  | `/api/search?categoryId={category_id}`                             | 상품 검색 필터링 (카테고리)                     |\n| `GET`  | `/api/search?attributeFilters[0][attr_key]=attr_value`             | 상품 검색 필터링 (상품 속성, ex {color : red} ) |\n| `GET`  | `/api/search?page={page:0}\u0026size={size:30}\u0026sort={key,(desc / asc)}` | 상품 검색 페이징 및 정렬                       |\n\n### Product\n\n| METHOD   | URI                                                     | 설명                      |\n|:---------|:--------------------------------------------------------|:------------------------|\n| `POST`   | `/api/products`                                         | 현재 로그인한 회원이 판매자라면 상품 생성 |\n| `GET`    | `/api/products?page={page:0}\u0026size={size:30}`            | 현재 로그인한 회원의 상품 정보 제공    |\n| `POST`   | `/api/products/{product_id}/details`                    | 상품의 상세 상품 생성            |\n| `PUT`    | `/api/products/{product_id}/details/{detail_id}`        | 상품의 상세 상품 수정            |\n| `DELETE` | `/api/products/{product_id}/details/{detail_id}`        | 상품의 상세 상품 삭제            |\n| `PATCH`  | `/api/products/{product_id}/details/{detail_id}/stocks` | 상품의 상세 상품 재고 수정         |\n\n### Order\n\n| METHOD   | URI                                                          | 설명                              |\n|:---------|:-------------------------------------------------------------|:--------------------------------|\n| `POST`   | `/api/orders`                                                | 주문 생성                           |\n| `GET`    | `/api/orders?last_timestamp={last_timestamp}\u0026size={size:10}` | 자신의 주문 정보 제공 (주문 시점을 키로 커서 페이징) |\n| `GET`    | `/api/orders/{order_id}`                                     | (주문의 소유주라면) 주문 가져오기             |\n| `DELETE` | `/api/orders/{order_id}`                                     | (주문의 소유주라면) 주문 삭제하기             |\n\n## 스크린샷\n\n### ERD\n\n![img.png](images/erd.png)\n\n### USECASE\n\n![usecase.png](/images/usecase.png)\n\n### 모니터링\n\n![monitoring.png](images/monitoring.png)\n\n### 로깅\n\n![elk.png](images/elk.png)\n\n### 성능 테스트\n\n| 최적화 전                               | SQL 최적화 후                                                          | REDIS 캐시 적용 후                       |\n|:------------------------------------|:-------------------------------------------------------------------|:------------------------------------|\n| \u003cimg src=\"/images/perf_test1.png\"/\u003e | \u003cimg src=\"/images/perf_test2.png\"/\u003e                                | \u003cimg src=\"/images/perf_test3.png\"/\u003e |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff-lab-edu%2Fjshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ff-lab-edu%2Fjshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff-lab-edu%2Fjshop/lists"}