-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 - DB 복제와 캐시] 알파카(최휘용) 미션 제출합니다. #71
base: slimsha2dy
Are you sure you want to change the base?
Changes from all commits
072f881
9c27395
4f0e630
6bb38a7
1479fb5
0b96898
8a21b06
ea7c748
2372b1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package coupon; | ||
|
||
import com.zaxxer.hikari.HikariDataSource; | ||
import jakarta.persistence.EntityManagerFactory; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import javax.sql.DataSource; | ||
import org.springframework.beans.factory.annotation.Qualifier; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.boot.jdbc.DataSourceBuilder; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.DependsOn; | ||
import org.springframework.context.annotation.Primary; | ||
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; | ||
import org.springframework.orm.jpa.JpaTransactionManager; | ||
import org.springframework.transaction.PlatformTransactionManager; | ||
|
||
@Configuration | ||
public class DataSourceConfig { | ||
|
||
public static final String READER = "reader"; | ||
public static final String WRITER = "writer"; | ||
|
||
@ConfigurationProperties(prefix = "coupon.datasource.writer") | ||
@Bean | ||
public DataSource writerDataSource() { | ||
return DataSourceBuilder.create().type(HikariDataSource.class).build(); | ||
} | ||
|
||
@ConfigurationProperties(prefix = "coupon.datasource.reader") | ||
@Bean | ||
public DataSource readerDataSource() { | ||
return DataSourceBuilder.create().type(HikariDataSource.class).build(); | ||
} | ||
|
||
@DependsOn({"writerDataSource", "readerDataSource"}) | ||
@Bean | ||
public DataSource routingDataSource( | ||
@Qualifier("writerDataSource") DataSource writer, | ||
@Qualifier("readerDataSource") DataSource reader) { | ||
ReadOnlyDataSourceRouter routingDataSource = new ReadOnlyDataSourceRouter(); | ||
|
||
Map<Object, Object> dataSourceMap = new HashMap<>(); | ||
|
||
dataSourceMap.put(WRITER, writer); | ||
dataSourceMap.put(READER, reader); | ||
|
||
routingDataSource.setTargetDataSources(dataSourceMap); | ||
routingDataSource.setDefaultTargetDataSource(writer); | ||
|
||
return routingDataSource; | ||
} | ||
|
||
@DependsOn({"routingDataSource"}) | ||
@Primary | ||
@Bean | ||
public DataSource dataSource(DataSource routingDataSource) { | ||
return new LazyConnectionDataSourceProxy(routingDataSource); | ||
} | ||
|
||
@Bean | ||
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { | ||
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); | ||
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory); | ||
return jpaTransactionManager; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package coupon; | ||
|
||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; | ||
import org.springframework.transaction.support.TransactionSynchronizationManager; | ||
|
||
public class ReadOnlyDataSourceRouter extends AbstractRoutingDataSource { | ||
|
||
@Override | ||
protected Object determineCurrentLookupKey() { | ||
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { | ||
return DataSourceConfig.READER; | ||
} | ||
return DataSourceConfig.WRITER; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package coupon.domain; | ||
|
||
public enum Category { | ||
|
||
FASHION, HOME_APPLIANCE, FURNITURE, FOOD | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package coupon.domain; | ||
|
||
import coupon.domain.coupon.Coupon; | ||
import coupon.domain.member.Member; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import java.time.LocalDate; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class MemberCoupon { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "coupon_id") | ||
private Coupon coupon; | ||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id") | ||
private Member member; | ||
private boolean used; | ||
private LocalDate issueDate; | ||
private LocalDate expirationDate; | ||
|
||
public MemberCoupon(Long id, Coupon coupon, Member member, boolean used, LocalDate issueDate) { | ||
this.id = id; | ||
this.coupon = coupon; | ||
this.member = member; | ||
this.used = used; | ||
this.issueDate = issueDate; | ||
this.expirationDate = issueDate.plusDays(6); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package coupon.domain.coupon; | ||
|
||
import coupon.domain.Category; | ||
import jakarta.persistence.Embedded; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Coupon { | ||
|
||
private static final int MINIMUM_DISCOUNT_RATE = 3; | ||
private static final int MAXIMUM_DISCOUNT_RATE = 20; | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
@Embedded | ||
private CouponName couponName; | ||
@Embedded | ||
private DiscountMount discountMount; | ||
@Embedded | ||
private MinimumMount minimumMount; | ||
private Category category; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 enum은 Enumerated 붙여주지 않아도 엔티티에 넣을 수 있군요..! |
||
@Embedded | ||
private Period period; | ||
|
||
public Coupon(CouponName couponName, DiscountMount discountMount, MinimumMount minimumMount, Category category, | ||
Period period) { | ||
validateDiscountRate(discountMount, minimumMount); | ||
this.couponName = couponName; | ||
this.discountMount = discountMount; | ||
this.minimumMount = minimumMount; | ||
this.category = category; | ||
this.period = period; | ||
} | ||
|
||
private void validateDiscountRate(DiscountMount discountMount, MinimumMount minimumMount) { | ||
float discountRate = (float) discountMount.getDiscountMount() / minimumMount.getMinimumMount() * 100; | ||
int truncatedDiscountRate = (int) discountRate; | ||
if (truncatedDiscountRate < MINIMUM_DISCOUNT_RATE || truncatedDiscountRate > MAXIMUM_DISCOUNT_RATE) { | ||
throw new IllegalArgumentException("쿠폰 할인율은 3% 이상, 20% 이하여야 합니다."); | ||
Comment on lines
+44
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
할인율을 검증하는 것 같은데 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 착각했네요.. 혼란을 줘서 죄송합니다.. discountrate를 별도의 객체로 분리하지 않은 이유가 있으신가요? |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package coupon.domain.coupon; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class CouponName { | ||
|
||
private static final int MAX_LENGTH = 30; | ||
|
||
private String couponName; | ||
|
||
public CouponName(String couponName) { | ||
validate(couponName); | ||
this.couponName = couponName; | ||
} | ||
|
||
private void validate(String name) { | ||
if (name == null || name.isEmpty()) { | ||
throw new IllegalArgumentException("이름은 비어있을 수 없습니다."); | ||
} | ||
if (name.length() > MAX_LENGTH) { | ||
throw new IllegalArgumentException("이름은 최대 " + MAX_LENGTH + "자 입니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package coupon.domain.coupon; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class DiscountMount { | ||
|
||
private static final int MINIMUM_MOUNT = 1_000; | ||
private static final int MAXIMUM_MOUNT = 10_000; | ||
private static final int UNIT = 500; | ||
|
||
private int discountMount; | ||
|
||
public DiscountMount(int discountMount) { | ||
validate(discountMount); | ||
this.discountMount = discountMount; | ||
} | ||
|
||
private void validate(int mount) { | ||
if (mount < MINIMUM_MOUNT) { | ||
throw new IllegalArgumentException("할인 금액은 " + MINIMUM_MOUNT + "원 이상이어야 합니다."); | ||
} | ||
if (mount > MAXIMUM_MOUNT) { | ||
throw new IllegalArgumentException("할인 금액은 " + MAXIMUM_MOUNT + "원 이하여야 합니다."); | ||
} | ||
if (mount % UNIT != 0) { | ||
throw new IllegalArgumentException("할인 금액은 " + UNIT + "원 단위여야 합니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package coupon.domain.coupon; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class MinimumMount { | ||
|
||
private static final int MINIMUM_MOUNT = 5_000; | ||
private static final int MAXIMUM_MOUNT = 100_000; | ||
|
||
private int minimumMount; | ||
|
||
public MinimumMount(int minimumMount) { | ||
validate(minimumMount); | ||
this.minimumMount = minimumMount; | ||
} | ||
|
||
private void validate(int mount) { | ||
if (mount < MINIMUM_MOUNT) { | ||
throw new IllegalArgumentException("최소 주문 금액은 " + MINIMUM_MOUNT + "원 이상이어야 합니다."); | ||
} | ||
if (mount > MAXIMUM_MOUNT) { | ||
throw new IllegalArgumentException("최소 주문 금액은 " + MAXIMUM_MOUNT + "원 이하여야 합니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package coupon.domain.coupon; | ||
|
||
import java.time.LocalDate; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
public class Period { | ||
|
||
private LocalDate startDate; | ||
private LocalDate endDate; | ||
|
||
public Period(LocalDate startDate, LocalDate endDate) { | ||
validate(startDate, endDate); | ||
this.startDate = startDate; | ||
this.endDate = endDate; | ||
} | ||
|
||
private void validate(LocalDate startDate, LocalDate endDate) { | ||
if (startDate.isAfter(endDate)) { | ||
throw new IllegalArgumentException("시작일은 종료일보다 이전이어야 합니다."); | ||
} | ||
} | ||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래의 요구사항은 어디서 반영이 되었을까요? 😄
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package coupon.domain.member; | ||
|
||
import jakarta.persistence.Embedded; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Member { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
@Embedded | ||
private MemberName memberName; | ||
|
||
public Member(Long id, MemberName memberName) { | ||
this.id = id; | ||
this.memberName = memberName; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package coupon.domain.member; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class MemberName { | ||
|
||
private static final int MAX_LENGTH = 30; | ||
|
||
private final String name; | ||
|
||
public MemberName(String name) { | ||
validate(name); | ||
this.name = name; | ||
} | ||
|
||
private void validate(String name) { | ||
if (name == null || name.isEmpty()) { | ||
throw new IllegalArgumentException("이름은 비어있을 수 없습니다."); | ||
} | ||
if (name.length() > MAX_LENGTH) { | ||
throw new IllegalArgumentException("이름은 최대 " + MAX_LENGTH + "자 입니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package coupon.repository; | ||
|
||
import coupon.domain.coupon.Coupon; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface CouponRepository extends JpaRepository<Coupon, Long> { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정말 궁금해서 질문드려요~~ 이 설정의 역할은 무엇인가요? 😄