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

Initial Commit

parents
package com.myshop.integration.infra;
import com.myshop.integration.OffsetStore;
import org.springframework.stereotype.Component;
@Component
public class MemoryOffsetStore implements OffsetStore {
private long nextOffset = 0;
@Override
public long get() {
return nextOffset;
}
@Override
public void update(long nextOffset) {
this.nextOffset = nextOffset;
}
}
package com.myshop.integration.infra;
import com.myshop.eventstore.api.EventEntry;
import com.myshop.integration.EventSender;
import org.springframework.stereotype.Component;
@Component
public class SysoutEventSender implements EventSender {
@Override
public void send(EventEntry event) {
System.out.println("EventSender send event : " + event);
}
}
package com.myshop.lock;
public class AlreadyLockedException extends LockException {
}
package com.myshop.lock;
public class LockData {
private String type;
private String id;
private String lockId;
private long timestamp;
public LockData(String type, String id, String lockId, long timestamp) {
this.type = type;
this.id = id;
this.lockId = lockId;
this.timestamp = timestamp;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getLockId() {
return lockId;
}
public long getTimestamp() {
return timestamp;
}
public boolean isExpired() {
return timestamp < System.currentTimeMillis();
}
}
\ No newline at end of file
package com.myshop.lock;
public class LockException extends RuntimeException {
public LockException() {
}
public LockException(String message) {
super(message);
}
public LockException(Throwable cause) {
super(cause);
}
}
package com.myshop.lock;
public class LockId {
private String value;
public LockId(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
package com.myshop.lock;
public interface LockManager {
LockId tryLock(String type, String id) throws LockException;
void checkLock(LockId lockId) throws LockException;
void releaseLock(LockId lockId) throws LockException;
void extendLockExpiration(LockId lockId, long inc) throws LockException;
}
package com.myshop.lock;
public class LockManagerException extends RuntimeException {
public LockManagerException(Exception cause) {
super(cause);
}
}
package com.myshop.lock;
public class LockingFailException extends LockException {
public LockingFailException() {
}
public LockingFailException(Exception cause) {
super(cause);
}
}
package com.myshop.lock;
public class NoLockException extends RuntimeException {
}
package com.myshop.lock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Component
public class SpringLockManager implements LockManager {
private int lockTimeout = 5 * 60 * 1000;
private JdbcTemplate jdbcTemplate;
private RowMapper<LockData> lockDataRowMapper = (rs, rowNum) ->
new LockData(rs.getString(1), rs.getString(2),
rs.getString(3), rs.getTimestamp(4).getTime());
public SpringLockManager(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public LockId tryLock(String type, String id) throws LockException {
checkAlreadyLocked(type, id);
LockId lockId = new LockId(UUID.randomUUID().toString());
locking(type, id, lockId);
return lockId;
}
private void checkAlreadyLocked(String type, String id) {
List<LockData> locks = jdbcTemplate.query(
"select * from locks where type = ? and id = ?",
lockDataRowMapper, type, id);
Optional<LockData> lockData = handleExpiration(locks);
if (lockData.isPresent()) throw new AlreadyLockedException();
}
private Optional<LockData> handleExpiration(List<LockData> locks) {
if (locks.isEmpty()) return Optional.empty();
LockData lockData = locks.get(0);
if (lockData.isExpired()) {
jdbcTemplate.update(
"delete from locks where type = ? and id = ?",
lockData.getType(), lockData.getId());
return Optional.empty();
} else {
return Optional.of(lockData);
}
}
private void locking(String type, String id, LockId lockId) {
try {
int updatedCount = jdbcTemplate.update(
"insert into locks values (?, ?, ?, ?)",
type, id, lockId.getValue(), new Timestamp(getExpirationTime()));
if (updatedCount == 0) throw new LockingFailException();
} catch (DuplicateKeyException e) {
throw new LockingFailException(e);
}
}
private long getExpirationTime() {
return System.currentTimeMillis() + lockTimeout;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void checkLock(LockId lockId) throws LockException {
Optional<LockData> lockData = getLockData(lockId);
if (!lockData.isPresent()) throw new NoLockException();
}
private Optional<LockData> getLockData(LockId lockId) {
List<LockData> locks = jdbcTemplate.query(
"select * from locks where lockid = ?",
lockDataRowMapper, lockId.getValue());
return handleExpiration(locks);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void extendLockExpiration(LockId lockId, long inc) throws LockException {
Optional<LockData> lockDataOpt = getLockData(lockId);
LockData lockData =
lockDataOpt.orElseThrow(() -> new NoLockException());
jdbcTemplate.update(
"update locks set expiration_time = ? where type = ? AND id = ?",
new Timestamp(lockData.getTimestamp() + inc),
lockData.getType(), lockData.getId());
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void releaseLock(LockId lockId) throws LockException {
jdbcTemplate.update("delete from locks where lockid = ?", lockId.getValue());
}
public void setLockTimeout(int lockTimeout) {
this.lockTimeout = lockTimeout;
}
}
\ No newline at end of file
package com.myshop.member.command.application;
import com.myshop.member.command.domain.Member;
import com.myshop.member.command.domain.MemberId;
import com.myshop.member.command.domain.MemberRepository;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BlockMemberService {
private MemberRepository memberRepository;
public BlockMemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@PreAuthorize("hasRole('ADMIN')")
@Transactional
public void block(String memberId) {
Member member = memberRepository.findById(new MemberId(memberId))
.orElseThrow(() -> new NoMemberException());
member.block();
}
}
package com.myshop.member.command.application;
public class NoMemberException extends RuntimeException {
}
package com.myshop.member.command.domain;
public class IdPasswordNotMatchingException extends RuntimeException {
}
package com.myshop.member.command.domain;
import com.myshop.common.event.Events;
import com.myshop.common.jpa.EmailSetConverter;
import com.myshop.common.model.Email;
import com.myshop.common.model.EmailSet;
import javax.persistence.*;
import java.util.Random;
import java.util.Set;
@Entity
@Table(name = "member")
public class Member {
@EmbeddedId
private MemberId id;
private String name;
@Embedded
private Password password;
private boolean blocked;
@Column(name = "emails")
@Convert(converter = EmailSetConverter.class)
private EmailSet emails;
protected Member() {
}
public Member(MemberId id, String name) {
this.id = id;
this.name = name;
}
public MemberId getId() {
return id;
}
public String getName() {
return name;
}
public void initializePassword() {
String newPassword = generateRandomPassword();
this.password = new Password(newPassword);
Events.raise(new PasswordChangedEvent(id.getId(), newPassword));
}
private String generateRandomPassword() {
Random random = new Random();
int number = random.nextInt();
return Integer.toHexString(number);
}
public void changeEmails(Set<Email> emails) {
this.emails = new EmailSet(emails);
}
public void block() {
this.blocked = true;
Events.raise(new MemberBlockedEvent(id.getId()));
}
public void unblock() {
this.blocked = false;
Events.raise(new MemberUnblockedEvent(id.getId()));
}
public void changePassword(String oldPw, String newPw) {
if (!password.match(oldPw)) {
throw new IdPasswordNotMatchingException();
}
this.password = new Password(newPw);
Events.raise(new PasswordChangedEvent(id.getId(), newPw));
}
public boolean isBlocked() {
return blocked;
}
public EmailSet getEmails() {
return emails;
}
}
package com.myshop.member.command.domain;
import com.myshop.common.event.Event;
public class MemberBlockedEvent extends Event {
private String memberId;
public MemberBlockedEvent(String memberId) {
this.memberId = memberId;
}
public String getMemberId() {
return memberId;
}
}
package com.myshop.member.command.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class MemberId implements Serializable {
@Column(name = "member_id")
private String id;
protected MemberId() {
}
public MemberId(String id) {
this.id = id;
}
public String getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemberId memberId = (MemberId) o;
return Objects.equals(id, memberId.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
public static MemberId of(String id) {
return new MemberId(id);
}
}
package com.myshop.member.command.domain;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import javax.persistence.LockModeType;
import javax.persistence.QueryHint;
import java.util.Optional;
public interface MemberRepository extends Repository<Member, MemberId> {
Optional<Member> findById(MemberId memberId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({
@QueryHint(name = "javax.persistence.lock.timeout", value = "3000")
})
@Query("select m from Member m where m.id = :id")
Optional<Member> findByIdForUpdate(@Param("id") MemberId memberId);
void save(Member member);
}
package com.myshop.member.command.domain;
public class MemberUnblockedEvent {
private String memberId;
public MemberUnblockedEvent(String memberId) {
this.memberId = memberId;
}
public String getMemberId() {
return memberId;
}
}
package com.myshop.member.command.domain;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Password {
@Column(name = "password")
private String value;
protected Password() {
}
public Password(String value) {
this.value = value;
}
public boolean match(String password) {
return this.value.equals(password);
}
}
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