Commit 969fb9e8 authored by madvirus's avatar madvirus
Browse files

Initial Commit

parents
package com.myshop.order.command.domain;
public interface CancelPolicy {
boolean hasCancellationPermission(Order order, Canceller canceller);
}
package com.myshop.order.command.domain;
public class Canceller {
private String memberId;
public Canceller(String memberId) {
this.memberId = memberId;
}
public String getMemberId() {
return memberId;
}
public static Canceller of(String memberId) {
return new Canceller(memberId);
}
}
package com.myshop.order.command.domain;
import com.myshop.common.event.Events;
import com.myshop.common.jpa.MoneyConverter;
import com.myshop.common.model.Money;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "purchase_order")
@Access(AccessType.FIELD)
public class Order {
@EmbeddedId
private OrderNo number;
@Version
private long version;
@Embedded
private Orderer orderer;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "order_line", joinColumns = @JoinColumn(name = "order_number"))
@OrderColumn(name = "line_idx")
private List<OrderLine> orderLines;
@Convert(converter = MoneyConverter.class)
@Column(name = "total_amounts")
private Money totalAmounts;
@Embedded
private ShippingInfo shippingInfo;
@Column(name = "state")
@Enumerated(EnumType.STRING)
private OrderState state;
@Column(name = "order_date")
private LocalDateTime orderDate;
protected Order() {
}
public Order(OrderNo number, Orderer orderer, List<OrderLine> orderLines,
ShippingInfo shippingInfo, OrderState state) {
setNumber(number);
setOrderer(orderer);
setOrderLines(orderLines);
setShippingInfo(shippingInfo);
this.state = state;
this.orderDate = LocalDateTime.now();
Events.raise(new OrderPlacedEvent(number.getNumber(), orderer, orderLines, orderDate));
}
private void setNumber(OrderNo number) {
if (number == null) throw new IllegalArgumentException("no number");
this.number = number;
}
private void setOrderer(Orderer orderer) {
if (orderer == null) throw new IllegalArgumentException("no orderer");
this.orderer = orderer;
}
private void setOrderLines(List<OrderLine> orderLines) {
verifyAtLeastOneOrMoreOrderLines(orderLines);
this.orderLines = orderLines;
calculateTotalAmounts();
}
private void verifyAtLeastOneOrMoreOrderLines(List<OrderLine> orderLines) {
if (orderLines == null || orderLines.isEmpty()) {
throw new IllegalArgumentException("no OrderLine");
}
}
private void calculateTotalAmounts() {
this.totalAmounts = new Money(orderLines.stream()
.mapToInt(x -> x.getAmounts().getValue()).sum());
}
private void setShippingInfo(ShippingInfo shippingInfo) {
if (shippingInfo == null) throw new IllegalArgumentException("no shipping info");
this.shippingInfo = shippingInfo;
}
public OrderNo getNumber() {
return number;
}
public long getVersion() {
return version;
}
public Orderer getOrderer() {
return orderer;
}
public Money getTotalAmounts() {
return totalAmounts;
}
public ShippingInfo getShippingInfo() {
return shippingInfo;
}
public OrderState getState() {
return state;
}
public void changeShippingInfo(ShippingInfo newShippingInfo) {
verifyNotYetShipped();
setShippingInfo(newShippingInfo);
Events.raise(new ShippingInfoChangedEvent(number, newShippingInfo));
}
public void cancel() {
verifyNotYetShipped();
this.state = OrderState.CANCELED;
Events.raise(new OrderCanceledEvent(number.getNumber()));
}
private void verifyNotYetShipped() {
if (!isNotYetShipped())
throw new AlreadyShippedException();
}
public boolean isNotYetShipped() {
return state == OrderState.PAYMENT_WAITING || state == OrderState.PREPARING;
}
public List<OrderLine> getOrderLines() {
return orderLines;
}
public boolean matchVersion(long version) {
return this.version == version;
}
public void startShipping() {
verifyShippableState();
this.state = OrderState.SHIPPED;
Events.raise(new ShippingStartedEvent(number.getNumber()));
}
private void verifyShippableState() {
verifyNotYetShipped();
verifyNotCanceled();
}
private void verifyNotCanceled() {
if (state == OrderState.CANCELED) {
throw new OrderAlreadyCanceledException();
}
}
}
package com.myshop.order.command.domain;
public class OrderAlreadyCanceledException extends RuntimeException {
}
package com.myshop.order.command.domain;
import com.myshop.common.event.Event;
public class OrderCanceledEvent extends Event {
private String orderNumber;
public OrderCanceledEvent(String number) {
super();
this.orderNumber = number;
}
public String getOrderNumber() {
return orderNumber;
}
}
package com.myshop.order.command.domain;
import com.myshop.catalog.command.domain.product.ProductId;
import com.myshop.common.jpa.MoneyConverter;
import com.myshop.common.model.Money;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
@Embeddable
public class OrderLine {
@Embedded
private ProductId productId;
@Convert(converter = MoneyConverter.class)
@Column(name = "price")
private Money price;
@Column(name = "quantity")
private int quantity;
@Convert(converter = MoneyConverter.class)
@Column(name = "amounts")
private Money amounts;
protected OrderLine() {
}
public OrderLine(ProductId productId, Money price, int quantity) {
this.productId = productId;
this.price = price;
this.quantity = quantity;
this.amounts = calculateAmounts();
}
private Money calculateAmounts() {
return price.multiply(quantity);
}
public ProductId getProductId() {
return productId;
}
public Money getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
public Money getAmounts() {
return amounts;
}
}
package com.myshop.order.command.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class OrderNo implements Serializable {
@Column(name = "order_number")
private String number;
protected OrderNo() {
}
public OrderNo(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderNo orderNo = (OrderNo) o;
return Objects.equals(number, orderNo.number);
}
@Override
public int hashCode() {
return Objects.hash(number);
}
public static OrderNo of(String number) {
return new OrderNo(number);
}
}
package com.myshop.order.command.domain;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
public class OrderPlacedEvent {
private String number;
private Orderer orderer;
private List<OrderLine> orderLines;
private LocalDateTime orderDate;
private OrderPlacedEvent() {
}
public OrderPlacedEvent(String number, Orderer orderer, List<OrderLine> orderLines, LocalDateTime orderDate) {
this.number = number;
this.orderer = orderer;
this.orderLines = orderLines;
this.orderDate = orderDate;
}
public String getNumber() {
return number;
}
public Orderer getOrderer() {
return orderer;
}
public List<OrderLine> getOrderLines() {
return orderLines;
}
public LocalDateTime getOrderDate() {
return orderDate;
}
}
package com.myshop.order.command.domain;
import org.springframework.data.repository.Repository;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
public interface OrderRepository extends Repository<Order, OrderNo> {
Optional<Order> findById(OrderNo id);
void save(Order order);
default OrderNo nextOrderNo() {
int randomNo = ThreadLocalRandom.current().nextInt(900000) + 100000;
String number = String.format("%tY%<tm%<td%<tH-%d", new Date(), randomNo);
return new OrderNo(number);
}
}
package com.myshop.order.command.domain;
public enum OrderState {
PAYMENT_WAITING, PREPARING, SHIPPED, DELIVERING, DELIVERY_COMPLETED, CANCELED
}
package com.myshop.order.command.domain;
import com.myshop.member.command.domain.MemberId;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.util.Objects;
@Embeddable
public class Orderer {
@AttributeOverrides(
@AttributeOverride(name = "id", column = @Column(name = "orderer_id"))
)
private MemberId memberId;
@Column(name = "orderer_name")
private String name;
protected Orderer() {
}
public Orderer(MemberId memberId, String name) {
this.memberId = memberId;
this.name = name;
}
public MemberId getMemberId() {
return memberId;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Orderer orderer = (Orderer) o;
return Objects.equals(memberId, orderer.memberId) &&
Objects.equals(name, orderer.name);
}
@Override
public int hashCode() {
return Objects.hash(memberId, name);
}
}
package com.myshop.order.command.domain;
import com.myshop.member.command.domain.MemberId;
public interface OrdererService {
Orderer createOrderer(MemberId ordererMemberId);
}
package com.myshop.order.command.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Receiver {
@Column(name = "receiver_name")
private String name;
@Column(name = "receiver_phone")
private String phone;
public Receiver() {
}
public Receiver(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
package com.myshop.order.command.domain;
import com.myshop.common.model.Address;
import javax.persistence.*;
@Embeddable
public class ShippingInfo {
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "zipCode", column = @Column(name = "shipping_zip_code")),
@AttributeOverride(name = "address1", column = @Column(name = "shipping_addr1")),
@AttributeOverride(name = "address2", column = @Column(name = "shipping_addr2"))
})
private Address address;
@Column(name = "shipping_message")
private String message;
@Embedded
private Receiver receiver;
public ShippingInfo() {
}
public ShippingInfo(Address address, String message, Receiver receiver) {
this.address = address;
this.message = message;
this.receiver = receiver;
}
public Address getAddress() {
return address;
}
public String getMessage() {
return message;
}
public Receiver getReceiver() {
return receiver;
}
}
package com.myshop.order.command.domain;
public class ShippingInfoChangedEvent {
private final OrderNo number;
private final ShippingInfo newShippingInfo;
private long timestamp;
public ShippingInfoChangedEvent(OrderNo number, ShippingInfo newShippingInfo) {
super();
this.number = number;
this.newShippingInfo = newShippingInfo;
this.timestamp = System.currentTimeMillis();
}
public OrderNo getNumber() {
return number;
}
public ShippingInfo getNewShippingInfo() {
return newShippingInfo;
}
public long getTimestamp() {
return timestamp;
}
}
package com.myshop.order.command.domain;
public class ShippingStartedEvent {
private String orderNumber;
public ShippingStartedEvent(String number) {
this.orderNumber = number;
}
public String getOrderNumber() {
return orderNumber;
}
}
package com.myshop.order.infra;
import com.myshop.order.command.application.RefundService;
import com.myshop.order.command.domain.OrderCanceledEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Service
public class OrderCanceledEventHandler {
private RefundService refundService;
public OrderCanceledEventHandler(RefundService refundService) {
this.refundService = refundService;
}
@Async
@TransactionalEventListener(
classes = OrderCanceledEvent.class,
phase = TransactionPhase.AFTER_COMMIT
)
public void handle(OrderCanceledEvent event) {
refundService.refund(event.getOrderNumber());
}
}
package com.myshop.order.infra;
import com.myshop.member.command.domain.MemberId;
import com.myshop.member.query.MemberData;
import com.myshop.member.query.MemberQueryService;
import com.myshop.order.command.domain.Orderer;
import com.myshop.order.command.domain.OrdererService;
import org.springframework.stereotype.Service;
@Service
public class OrdererServiceImpl implements OrdererService {
private MemberQueryService memberQueryService;
public OrdererServiceImpl(MemberQueryService memberQueryService) {
this.memberQueryService = memberQueryService;
}
@Override
public Orderer createOrderer(MemberId ordererMemberId) {
MemberData memberData = memberQueryService.getMemberData(ordererMemberId.getId());
return new Orderer(MemberId.of(memberData.getId()), memberData.getName());
}
}
package com.myshop.order.infra.domain;
import com.myshop.order.command.domain.CancelPolicy;
import com.myshop.order.command.domain.Canceller;
import com.myshop.order.command.domain.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class SecurityCancelPolicy implements CancelPolicy {
@Override
public boolean hasCancellationPermission(Order order, Canceller canceller) {
return isCancellerOrderer(order, canceller) || isCurrentUserAdminRole();
}
private boolean isCancellerOrderer(Order order, Canceller canceller) {
return order.getOrderer().getMemberId().getId().equals(canceller.getMemberId());
}
private boolean isCurrentUserAdminRole() {
SecurityContext context = SecurityContextHolder.getContext();
if (context == null) return false;
Authentication authentication = context.getAuthentication();
if (authentication == null) return false;
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
if (authorities == null) return false;
return authorities.stream().anyMatch(authority -> authority.getAuthority().equals("ROLE_ADMIN"));
}
}
package com.myshop.order.infra.paygate;
import com.myshop.order.command.application.RefundService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class ExternalRefundService implements RefundService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void refund(String orderNumber) {
logger.info("refund order[{}]", orderNumber);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment