๐ ๋๋ง ์๊ณ ์๋ ์ข์ ์ข์์ด๋ ๊ฟ ๊ฐ์ ์ข์์ ๊ณต์ ํ๊ณ ์ถ์ผ์ ๋ถ!
๐ ํค ํฐ ์ฌ๋๋ ํค ์์ ์ฌ๋๋ ๊ณต์ฐ์ด ๋ค ๋ณด์ด๋ ์๋ฆฌ๋ฅผ ๊ณต์ ํ๊ณ ์ถ์ผ์ ๋ถ !
๐ ๊ทน์ฅ ์ข์์ด ์ด๋ป๊ฒ ๋ณด์ผ์ง ๋ชจ๋ฅด๊ฒ ๋ค๋ฉด ํฌ๋์์์ ํ์ธํ์ธ์!
- ํฌ๋์์ ์จ๊ฒจ์ง ๊ฟ ์๋ฆฌ๋ ์ต์ ์ ์ข์ ์ ํ์ ๋์ธ ์ ์๋ ์น ์ฌ์ดํธ ์ ๋๋ค.
๐ ์น์ฌ์ดํธ | Website ํฌ๋์ ๋ฐ๋ก๊ฐ๊ธฐ
- ๊ฐ๋ฐ๊ธฐ๊ฐ | Project Period
- ์ํคํ ์ณ | Architecture
- ์ฃผ์ ๊ธฐ๋ฅ | Main Function
- ๊ฐ๋ฐํ๊ฒฝ | Development Enviornment
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ | Library
- ERD
- ํธ๋ฌ๋ธ ์ํ | Trouble shooting
- ํ์ | TEAM
2022.08.26 ~ 2022.10.03 (6์ฃผ๊ฐ)
- ์์ ๋ก๊ทธ์ธ (ํธ์ํฐ / ์นด์นด์ค)
- ์ค์๊ฐ ๋ฆฌ๋ทฐ ํ์ธ (๋ฉ์ธ ํ์ด์ง ์ค์๊ฐ ๋ฆฌ๋ทฐ SECTION)
- ๋ฆฌ๋ทฐ ํ๊ธฐ (CRUD) / ๋ฆฌ๋ทฐ ๋๊ธ / ์ข์์ ๊ธฐ๋ฅ / ๋ฆฌ๋ทฐ ํ์ด์ง ๋ฌดํ์คํฌ๋กค
- ํ๊ทธ, ํํฐ, ๊ฒ์์ด๋ฅผ ํตํ ๊ฒ์ ๊ธฐ๋ฅ
- ํ๋กํ ์์
- ํ๋กํ ๋ด์์ ๋ด๊ฐ ์ด ๋ฆฌ๋ทฐ ํ์ธ
- ๊ทน์ฅ ์ ๋ณด ์ธํฌ MODAL (KAKAO MAP)
- ๋ฐ์ํ
- ๊ฐํธํ ์์ ๋ก๊ทธ์ธ ํธ์ํฐ, ์นด์นด์คํก ์ง์
- ๋ค์ํ ๊ฒฝ์ฐ์ ๊ฒ์ ์์ฒญ ์ผ๊ด ์ฒ๋ฆฌ (QueryDSL)
- ์์ฃผ ์กฐํํ๋ ๋ฐ์ดํฐ ์บ์ฑ ์ฒ๋ฆฌ (Redis, Spring Cache)
- ๋ฐฐํฌ๋ ์๋ฒ์ ์๋ฌ ๋ก๊ทธ๋ฅผ ์ฝ๊ฒ ํ์ธ ๊ฐ๋ฅ (Slack Webhook, Logback)
- RefreshToken ํ ํฐ์ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธ ์ ๋ณด ์๋ ๊ฐฑ์ (JWT)
- S3 ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋๋ ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง ์ฒ๋ฆฌ (Imgscalr)
- CI/CD ์๋ ๋ฐฐํฌ (Githib Action, S3, CodeDeploy)
- ๋ฌด์ค๋จ ๋ฐฐํฌ (NGINX)
"@fortawesome/react-fontawesome": "^0.2.0",
"axios": "^0.27.2",
"file-loader": "^6.2.0",
"json-server": "^0.17.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.34.2",
"react-intersection-observer": "^9.4.0",
"react-query": "^3.39.2",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-select": "^5.4.0",
"react-toastify": "^9.0.8",
"recoil": "^0.7.5",
"styled-components": "^5.3.5",
"sweetalert2": "^11.4.33",
"sweetalert2-react-content": "^5.0.3",
"swiper": "^8.3.2",
(FE) ์น ํ์ด์ง ์ฑ๋ฅ ์ต์ ํ ํ๊ธฐ
lighthouse๋ฅผ ์ด์ฉํ ์ฑ๋ฅ ์ธก์ ๊ฒฐ๊ณผ ์ฑ๋ฅ ๋ฐ ์ ๊ทผ์ฑ, ๊ถ์ฅ์ฌํญ์ ์ ์๊ฐ ๋ง์กฑ์ค๋ฝ๊ฒ ๋์ค์ง ์์ ํด๋น ์ฌํญ๋ค์ ์ ๋ฆฌํ๊ณ , ๊ฐ์ ํ๊ธฐ๋ก ๊ฒฐ์ .
ํธ๋ฌ๋ธ ์ํ
์ด๋ฏธ์ง ๋ ๋๋ง ์๊ฐ์ ์ค์ด๊ธฐ ์ํด
- ์ฐจ์ธ๋ ํ์์ ์ฌ์ฉํด ์ด๋ฏธ์ง๋ฅผ ์ ๊ณต jpg, png > webp๋ก ๋ณ๊ฒฝ
- ๋์ ์ด๋ฏธ์ง ์บ์ฑ์ฒ๋ฆฌ ์งํ. s3, cloudfront ์์ ์บ์ฑ ์ ์ฑ ๋ณ๊ฒฝ
๋ฉ์ธํ์ด์ง ๋ ๋๋ง์ ์ฐจ๋จ์ํค๊ณ ๋ฆฌ์์ค๋ฅผ ๋จผ์ ๋ ๋๋ง ํ๋ ค๋๊ฑธ ๋ฐฉ์ง.
(๋ฆฌ์์ค ๋งํฌ์ async๋ฅผ ๋ฃ์ด ๊ฐ์ด ๋ก๋ฉ ์ํค๋๋กํจ.)
์นํฐํธ๊ฐ ๋ก๋ฉ๋๊ธฐ ์ ๊น์ง ํ
์คํธ๊ฐ ํ์๋์ง ์๋๋ก ์ต์ ํ๋ฅผ ์งํํจ.
(font-display : swap;)
lighthouse ๊ฒฐ๊ณผ
(FE) ์ด๋ฏธ์ง ๋ ๋๋ง ์๋ ๊ฐ์
์ฌ์ฉ์๊ฐ ๋ฎค์ง์ปฌ ์ข์ ๋ฆฌ๋ทฐ๋ฅผ ํ์ธํ๊ธฐ ์ํด ์ฌ๋ฌ ์๋ก๋ค๋ฅธ ์ฌ๋ฌ๊ฐ์ง ๋ฎค์ง์ปฌ์ ์กฐํํ๊ณ ๋ค์ ๊ฐ์ ๋ฎค์ง์ปฌ์ ์กฐํํ ๋๋ง๋ค ๋ ๋๋ง์ด ๊ณ์ ์ผ์ด๋๋ค๋ฉด ์ฌ์ฉ์ ํผ๋ก๋๊ฐ ์ฌ๋ผ๊ฐ ์ดํ๋ฅ ์ด ๋์์ง ๊ฒ์ผ๋ก ์์
ํธ๋ฌ๋ธ ์ํ
๋ฆฌ์กํธ ์ฟผ๋ฆฌ์ ์บ์ฑ ๊ธฐ๋ฅ์ ์ฌ์ฉํด ๋ฐ๋ก ์ฌ์ฉ์์๊ฒ ๋ ๋๋ง ๋ ํ๋ฉด์ ์ถ๋ ฅ
(BE) ๊ทน์ฅ๋ณ ์ข์ ์กฐํ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ฑ๋ฅ ๊ฐ์
- ๊ทน์ฅ๋ณ ์ข์ ์ ๋ณด๋ฅผ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ํ ์ด๋ธ์ ์ ์ฅํ์์ต๋๋ค.
| ์ข์์์ด๋ | ์ธต | ๊ตฌ์ญ | ์ด | ์ข์๋ฒํธ | ๊ทน์ฅ์์ด๋ |
|---|---|---|---|---|---|
| 1 | FIRST | A | 1 | 8 | 1 |
| 2 | FIRST | A | 1 | 9 | 1 |
- ๊ทน์ฅ์ ์ข์ ์ ๋ณด ๋ฐ์ดํฐ๋ฅผ json ํํ๋ก ๊ฐ๊ณตํ๋ ๊ณผ์ ์์ ์ฌ๋ฌ๋ฒ์ ์ฟผ๋ฆฌ ํธ์ถ์ด ํ์ํ๊ณ ์๋ฒ ์๋ต์ ๋ง์ ์๊ฐ์ด ์์๋์์ต๋๋ค.
์๊ฒฌ์กฐ์จ
- mongoDB ๊ฐ์ NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ข์ ์ ๋ณด๋ฅผ json ํ์์ผ๋ก ์ ์ฅํ๊ธฐ
- ์บ์๋ฅผ ์ ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ์ถ ํ์๋ฅผ ์ค์ด๊ธฐ
- ๊ทน์ฅ๋ณ ์ข์ ์ ๋ณด๋ ๋ณ๊ฒฝ์ด ๊ทน๋๋ก ์ ๊ณ ์กฐํ๊ฐ ๋ง์ ๋ฐ์ดํฐ ํน์ฑ์ ๊ฐ์ง๊ณ ์์
- ์บ์๋ฅผ ์ ์ฉํ๊ธฐ๋ก ๊ฒฐ์
๊ฒฐ๊ณผ
- JMeter๋ฅผ ํ์ฉํ ํ ์คํธ ๊ฒฐ๊ณผ ์ฝ 90%์ ์๋ต์๊ฐ ๊ฐ์๋ฅผ ํ์ธํ ์ ์์์ต๋๋ค.
| ๋ผ๋ฒจ | ํ๋ณธ์ | ํ๊ท (ms) | ์ต์๊ฐ | ์ต๋๊ฐ | ํ์คํธ์ฐจ | ์ค๋ฅ |
|---|---|---|---|---|---|---|
| cache | 5000 | 341 | 5 | 725 | 156.5247 | 0 |
| noCache | 5000 | 3284 | 43 | 12071 | 1014.649 | 0 |
(BE) ๊ทน์ฅ๋ณ ์ข์ ์กฐํ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ฑ๋ฅ ๊ฐ์
- JWT ํ ํฐ์ ํฌํจํ๋ ๋ชจ๋ ์์ฒญ์ ํํฐ์์ ํด๋น ํ ํฐ์ ๊ฒ์ฆํ๋ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค.
- MemberRepository๋ฅผ ํตํด Member ๊ฐ์ฒด๋ฅผ ์ฐพ์์์ UserDetails๋ฅผ ๊ตฌ์ฑํ์๋๋ฐ
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDetailsImpl implements UserDetails {
private Member member;
...
}
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String subject) throws UsernameNotFoundException {
Member member = memberRepository.findById(Long.parseLong(subject))
.orElseThrow(() -> new UsernameNotFoundException("๋ฑ๋ก๋์ง์์ ์ฌ์ฉ์์
๋๋ค."));
return new UserDetailsImpl(member);
}
}- ์ฌ์ฉ์ ๊ถํ์ด ํ์ ์๋ ์์ฒญ์๋ ํ ํฐ์ด ํฌํจ๋์ด์๋ค๋ฉด UserDetails๋ฅผ ์์ฑํ๋ ๊ณผ์ ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์กฐํํ์ฌ ์๋ต์๊ฐ์ด ๊ธธ์ด์ง๋ ๊ฒ์ ํ์ธํ์์ต๋๋ค.
์์ ๋ฐฉํฅ
- JWT Token์ ์๋ฒ์์ SecretKey๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆํ ๊ฒ
- Token์ ๋ด๊ธด ์์ด๋๋ ํ ํฐ์ด ์ ํจํ๋ค๋ฉด ์ ํํ ๊ฒ
-
UserDetaileService๋ฅผ ์ญ์ ํ๊ณ , UserDetailsImpl์ Member ๊ฐ์ฒด ๋์ Id๋ฅผ ์ถ๊ฐํ๊ณ
-
JwtTokenProvider์์ ์ธ์ฆ์ ๋ณด๋ฅผ ์กฐํํ ๋, JWT ํ ํฐ์ Sub ํ๋์ ์ ์ฅํด๋ id๊ฐ์ผ๋ก UserDetails๋ฅผ ์์ฑํ์์ต๋๋ค.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDetailsImpl implements UserDetails {
private Long memberId;
...
}
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtTokenProvider {
...
public Authentication getAuthentication(String jwtToken) {
Claims claims = getClaims(jwtToken);
UserDetailsImpl userDetails = new UserDetailsImpl(Long.parseLong(laims.getSubject()));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
...
}- ๊ฐ๊ฐ์ ์ํฐํฐ๋ค๊ณผ Member ๊ฐ์ฒด๊ฐ ๋งบ์ ์ฐ๊ด๊ด๊ณ๋ ์ ๊ฑฐํ ํ์ Auditing ์์ฑ์ @CreatedBy @LastModifiedBy ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ์์ต๋๋ค.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
...
@CreatedBy // ์์ฑ์
private Long createdBy;
@LastModifiedBy //์์ ์
private Long modifiedBy;
}
@RequiredArgsConstructor
@Component
public class LoginUserAuditorAware implements AuditorAware<Long> {
@Override
public Optional<Long> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null == authentication || !authentication.isAuthenticated() || authentication.getPrincipal().equals("anonymousUser")) {
return Optional.empty();
}
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
return Optional.ofNullable(userDetails.getMemberId());
}
}๊ฒฐ๊ณผ
- JMeter๋ฅผ ํ์ฉํ ํ ์คํธ ๊ฒฐ๊ณผ ์ฝ 80%์ ์๋ต์๊ฐ ๊ฐ์๋ฅผ ํ์ธํ ์ ์์์ต๋๋ค.
| ๋ผ๋ฒจ | ํ๋ณธ์ | ํ๊ท (ms) | ์ต์๊ฐ | ์ต๋๊ฐ | ํ์คํธ์ฐจ | ์ค๋ฅ |
|---|---|---|---|---|---|---|
| ๋ฉค๋ฒ ๊ฐ์ฒด ์ฌ์ฉ | 5000 | 939 | 34 | 1893 | 199.3258 | 0 |
| Token id๊ฐ ์ฌ์ฉ | 5000 | 125 | 7 | 382 | 53.45967 | 0 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|---|---|
| ๊น ํ | ๊นํ๋ฆผ | ๊น์๋ฆผ | ๋ฐ์์ | ๋ฐ์ฉ์ | ๊น์ ๋ฆฌ |
| BE(๋ฆฌ๋) | BE | FE(๋ถ๋ฆฌ๋) | FE | FE | DESINGER |
| ๐ | ๐ | ๐ | ๐ | ๐ | ๐ |













