AuctionEntity example in Java, #25485
This commit is contained in:
parent
70176341d9
commit
b1b959df50
8 changed files with 852 additions and 0 deletions
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An auction.
|
||||
*/
|
||||
public final class Auction {
|
||||
/**
|
||||
* The item under auction.
|
||||
*/
|
||||
private final UUID itemId;
|
||||
/**
|
||||
* The user that created the item.
|
||||
*/
|
||||
private final UUID creator;
|
||||
/**
|
||||
* The reserve price of the auction.
|
||||
*/
|
||||
private final int reservePrice;
|
||||
/**
|
||||
* The minimum increment between bids.
|
||||
*/
|
||||
private final int increment;
|
||||
/**
|
||||
* The time the auction started.
|
||||
*/
|
||||
private final Instant startTime;
|
||||
/**
|
||||
* The time the auction will end.
|
||||
*/
|
||||
private final Instant endTime;
|
||||
|
||||
public Auction(UUID itemId, UUID creator, int reservePrice, int increment, Instant startTime, Instant endTime) {
|
||||
this.itemId = itemId;
|
||||
this.creator = creator;
|
||||
this.reservePrice = reservePrice;
|
||||
this.increment = increment;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public UUID getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public UUID getCreator() {
|
||||
return creator;
|
||||
}
|
||||
|
||||
public int getReservePrice() {
|
||||
return reservePrice;
|
||||
}
|
||||
|
||||
public int getIncrement() {
|
||||
return increment;
|
||||
}
|
||||
|
||||
public Instant getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public Instant getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import akka.Done;
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.persistence.typed.ExpectingReply;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An auction command.
|
||||
*/
|
||||
public interface AuctionCommand {
|
||||
|
||||
/**
|
||||
* Start the auction.
|
||||
*/
|
||||
final class StartAuction implements AuctionCommand, ExpectingReply<Done> {
|
||||
|
||||
/**
|
||||
* The auction to start.
|
||||
*/
|
||||
private final Auction auction;
|
||||
|
||||
private final ActorRef<Done> replyTo;
|
||||
|
||||
public StartAuction(Auction auction, ActorRef<Done> replyTo) {
|
||||
this.auction = auction;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<Done> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
|
||||
public Auction getAuction() {
|
||||
return auction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the auction.
|
||||
*/
|
||||
final class CancelAuction implements AuctionCommand, ExpectingReply<Done> {
|
||||
private final ActorRef<Done> replyTo;
|
||||
|
||||
public CancelAuction(ActorRef<Done> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<Done> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a bid on the auction.
|
||||
*/
|
||||
final class PlaceBid implements AuctionCommand, ExpectingReply<PlaceBidReply> {
|
||||
|
||||
private final int bidPrice;
|
||||
private final UUID bidder;
|
||||
|
||||
private final ActorRef<PlaceBidReply> replyTo;
|
||||
|
||||
public PlaceBid(int bidPrice, UUID bidder, ActorRef<PlaceBidReply> replyTo) {
|
||||
this.bidPrice = bidPrice;
|
||||
this.bidder = bidder;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<PlaceBidReply> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
|
||||
public int getBidPrice() {
|
||||
return bidPrice;
|
||||
}
|
||||
|
||||
public UUID getBidder() {
|
||||
return bidder;
|
||||
}
|
||||
}
|
||||
|
||||
interface PlaceBidReply {}
|
||||
|
||||
/**
|
||||
* The status of placing a bid.
|
||||
*/
|
||||
enum PlaceBidStatus {
|
||||
/**
|
||||
* The bid was accepted, and is the current highest bid.
|
||||
*/
|
||||
ACCEPTED(BidResultStatus.ACCEPTED),
|
||||
/**
|
||||
* The bid was accepted, but was outbidded by the maximum bid of the current highest bidder.
|
||||
*/
|
||||
ACCEPTED_OUTBID(BidResultStatus.ACCEPTED_OUTBID),
|
||||
/**
|
||||
* The bid was accepted, but is below the reserve.
|
||||
*/
|
||||
ACCEPTED_BELOW_RESERVE(BidResultStatus.ACCEPTED_BELOW_RESERVE),
|
||||
/**
|
||||
* The bid was not at least the current bid plus the increment.
|
||||
*/
|
||||
TOO_LOW(BidResultStatus.TOO_LOW),
|
||||
/**
|
||||
* The auction hasn't started.
|
||||
*/
|
||||
NOT_STARTED(BidResultStatus.NOT_STARTED),
|
||||
/**
|
||||
* The auction has already finished.
|
||||
*/
|
||||
FINISHED(BidResultStatus.FINISHED),
|
||||
/**
|
||||
* The auction has been cancelled.
|
||||
*/
|
||||
CANCELLED(BidResultStatus.CANCELLED);
|
||||
|
||||
public final BidResultStatus bidResultStatus;
|
||||
|
||||
PlaceBidStatus(BidResultStatus bidResultStatus) {
|
||||
this.bidResultStatus = bidResultStatus;
|
||||
}
|
||||
|
||||
public static PlaceBidStatus from(BidResultStatus status) {
|
||||
switch (status) {
|
||||
case ACCEPTED:
|
||||
return ACCEPTED;
|
||||
case ACCEPTED_BELOW_RESERVE:
|
||||
return ACCEPTED_BELOW_RESERVE;
|
||||
case ACCEPTED_OUTBID:
|
||||
return ACCEPTED_OUTBID;
|
||||
case CANCELLED:
|
||||
return CANCELLED;
|
||||
case FINISHED:
|
||||
return FINISHED;
|
||||
case NOT_STARTED:
|
||||
return NOT_STARTED;
|
||||
case TOO_LOW:
|
||||
return TOO_LOW;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of placing a bid.
|
||||
*/
|
||||
final class PlaceBidResult implements PlaceBidReply {
|
||||
|
||||
/**
|
||||
* The current price of the auction.
|
||||
*/
|
||||
private final int currentPrice;
|
||||
/**
|
||||
* The status of the attempt to place a bid.
|
||||
*/
|
||||
private final PlaceBidStatus status;
|
||||
/**
|
||||
* The current winning bidder.
|
||||
*/
|
||||
private final UUID currentBidder;
|
||||
|
||||
public PlaceBidResult(PlaceBidStatus status, int currentPrice, UUID currentBidder) {
|
||||
this.currentPrice = currentPrice;
|
||||
this.status = status;
|
||||
this.currentBidder = currentBidder;
|
||||
}
|
||||
|
||||
public int getCurrentPrice() {
|
||||
return currentPrice;
|
||||
}
|
||||
|
||||
public PlaceBidStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public UUID getCurrentBidder() {
|
||||
return currentBidder;
|
||||
}
|
||||
}
|
||||
|
||||
final class PlaceBidRejected implements PlaceBidReply {
|
||||
private final String errorMessage;
|
||||
|
||||
public PlaceBidRejected(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish bidding.
|
||||
*/
|
||||
final class FinishBidding implements AuctionCommand, ExpectingReply<Done> {
|
||||
|
||||
private final ActorRef<Done> replyTo;
|
||||
|
||||
FinishBidding(ActorRef<Done> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<Done> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auction.
|
||||
*/
|
||||
final class GetAuction implements AuctionCommand, ExpectingReply<AuctionState> {
|
||||
private final ActorRef<AuctionState> replyTo;
|
||||
|
||||
public GetAuction(ActorRef<AuctionState> replyTo) {
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorRef<AuctionState> replyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import akka.Done;
|
||||
import akka.persistence.typed.ExpectingReply;
|
||||
import akka.persistence.typed.PersistenceId;
|
||||
import akka.persistence.typed.javadsl.CommandHandler;
|
||||
import akka.persistence.typed.javadsl.CommandHandlerBuilder;
|
||||
import akka.persistence.typed.javadsl.Effect;
|
||||
import akka.persistence.typed.javadsl.EventHandler;
|
||||
import akka.persistence.typed.javadsl.PersistentBehavior;
|
||||
|
||||
import static jdocs.akka.persistence.typed.auction.AuctionCommand.*;
|
||||
import static jdocs.akka.persistence.typed.auction.AuctionEvent.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Based on https://github.com/lagom/online-auction-java/blob/master/bidding-impl/src/main/java/com/example/auction/bidding/impl/AuctionEntity.java
|
||||
*/
|
||||
public class AuctionEntity extends PersistentBehavior<AuctionCommand, AuctionEvent, AuctionState> {
|
||||
|
||||
private final UUID entityUUID;
|
||||
|
||||
public AuctionEntity(String entityId) {
|
||||
// when used with Cluster Sharding this should use EntityTypeKey, or PersistentEntity
|
||||
super(new PersistenceId("Auction|" + entityId));
|
||||
this.entityUUID = UUID.fromString(entityId);
|
||||
}
|
||||
|
||||
// Command handler for the not started state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> notStartedHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.NOT_STARTED)
|
||||
.matchCommand(StartAuction.class, this::startAuction)
|
||||
.matchCommand(PlaceBid.class, (state, cmd) -> Effect().reply(cmd, createResult(state, PlaceBidStatus.NOT_STARTED)));
|
||||
|
||||
// Command handler for the under auction state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> underAuctionHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.UNDER_AUCTION)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(PlaceBid.class, this::placeBid)
|
||||
.matchCommand(FinishBidding.class, this::finishBidding);
|
||||
|
||||
// Command handler for the completed state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> completedHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.COMPLETE)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(FinishBidding.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(PlaceBid.class, (state, cmd) -> Effect().reply(cmd, createResult(state, PlaceBidStatus.FINISHED)));
|
||||
|
||||
// Command handler for the cancelled state.
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> cancelledHandler =
|
||||
commandHandlerBuilder(state -> state.getStatus() == AuctionStatus.CANCELLED)
|
||||
.matchCommand(StartAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(FinishBidding.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(CancelAuction.class, (state, cmd) -> alreadyDone(cmd))
|
||||
.matchCommand(PlaceBid.class, (state, cmd) -> Effect().reply(cmd, createResult(state, PlaceBidStatus.CANCELLED)));
|
||||
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> getAuctionHandler =
|
||||
commandHandlerBuilder(AuctionState.class)
|
||||
.matchCommand(GetAuction.class, (state, cmd) -> Effect().reply(cmd, state));
|
||||
|
||||
private CommandHandlerBuilder<AuctionCommand, AuctionEvent, AuctionState, AuctionState> cancelHandler =
|
||||
commandHandlerBuilder(AuctionState.class)
|
||||
.matchCommand(CancelAuction.class, this::cancelAuction);
|
||||
// Note, an item can go from completed to cancelled, since it is the item service that controls
|
||||
// whether an auction is cancelled or not. If it cancels before it receives a bidding finished
|
||||
// event from us, it will ignore the bidding finished event, so we need to update our state
|
||||
// to reflect that.
|
||||
|
||||
|
||||
private Effect<AuctionEvent, AuctionState> startAuction(AuctionState state, StartAuction cmd) {
|
||||
return Effect().persist(new AuctionStarted(entityUUID, cmd.getAuction()))
|
||||
.thenReply(cmd, __ -> Done.getInstance());
|
||||
}
|
||||
|
||||
private Effect<AuctionEvent, AuctionState> finishBidding(AuctionState state, FinishBidding cmd) {
|
||||
return Effect().persist(new BiddingFinished(entityUUID))
|
||||
.thenReply(cmd, __ -> Done.getInstance());
|
||||
}
|
||||
|
||||
private Effect<AuctionEvent, AuctionState> cancelAuction(AuctionState state, CancelAuction cmd) {
|
||||
return Effect().persist(new AuctionCancelled(entityUUID))
|
||||
.thenReply(cmd, __ -> Done.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* The main logic for handling of bids.
|
||||
*/
|
||||
private Effect<AuctionEvent, AuctionState> placeBid(AuctionState state, PlaceBid bid) {
|
||||
Auction auction = state.getAuction().get();
|
||||
|
||||
Instant now = Instant.now();
|
||||
|
||||
// Even though we're not in the finished state yet, we should check
|
||||
if (auction.getEndTime().isBefore(now)) {
|
||||
return Effect().reply(bid, createResult(state, PlaceBidStatus.FINISHED));
|
||||
}
|
||||
|
||||
if (auction.getCreator().equals(bid.getBidder())) {
|
||||
return Effect().reply(bid, new PlaceBidRejected("An auctions creator cannot bid in their own auction."));
|
||||
}
|
||||
|
||||
Optional<Bid> currentBid = state.lastBid();
|
||||
int currentBidPrice;
|
||||
int currentBidMaximum;
|
||||
if (currentBid.isPresent()) {
|
||||
currentBidPrice = currentBid.get().getBidPrice();
|
||||
currentBidMaximum = currentBid.get().getMaximumBid();
|
||||
} else {
|
||||
currentBidPrice = 0;
|
||||
currentBidMaximum = 0;
|
||||
}
|
||||
|
||||
boolean bidderIsCurrentBidder = currentBid.filter(b -> b.getBidder().equals(bid.getBidder())).isPresent();
|
||||
|
||||
if (bidderIsCurrentBidder && bid.getBidPrice() >= currentBidPrice) {
|
||||
// Allow the current bidder to update their bid
|
||||
if (auction.getReservePrice()>currentBidPrice) {
|
||||
|
||||
int newBidPrice = Math.min(auction.getReservePrice(), bid.getBidPrice());
|
||||
PlaceBidStatus placeBidStatus;
|
||||
|
||||
if (newBidPrice == auction.getReservePrice()) {
|
||||
placeBidStatus = PlaceBidStatus.ACCEPTED;
|
||||
}
|
||||
else {
|
||||
placeBidStatus = PlaceBidStatus.ACCEPTED_BELOW_RESERVE;
|
||||
}
|
||||
return Effect().persist(new BidPlaced(entityUUID,
|
||||
new Bid(bid.getBidder(), now, newBidPrice, bid.getBidPrice())))
|
||||
.thenReply(bid, newState -> new PlaceBidResult(placeBidStatus, newBidPrice, bid.getBidder()));
|
||||
}
|
||||
return Effect().persist(new BidPlaced(entityUUID,
|
||||
new Bid(bid.getBidder(), now, currentBidPrice, bid.getBidPrice())))
|
||||
.thenReply(bid, newState -> new PlaceBidResult(PlaceBidStatus.ACCEPTED, currentBidPrice, bid.getBidder()));
|
||||
}
|
||||
|
||||
if (bid.getBidPrice() < currentBidPrice + auction.getIncrement()) {
|
||||
return Effect().reply(bid, createResult(state, PlaceBidStatus.TOO_LOW));
|
||||
} else if (bid.getBidPrice() <= currentBidMaximum) {
|
||||
return handleAutomaticOutbid(bid, auction, now, currentBid, currentBidPrice, currentBidMaximum);
|
||||
} else {
|
||||
return handleNewWinningBidder(bid, auction, now, currentBidMaximum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the situation where a bid will be accepted, but it will be automatically outbid by the current bidder.
|
||||
*
|
||||
* This emits two events, one for the bid currently being replace, and another automatic bid for the current bidder.
|
||||
*/
|
||||
private Effect<AuctionEvent, AuctionState> handleAutomaticOutbid(
|
||||
PlaceBid bid, Auction auction, Instant now, Optional<Bid> currentBid, int currentBidPrice, int currentBidMaximum) {
|
||||
// Adjust the bid so that the increment for the current maximum makes the current maximum a valid bid
|
||||
int adjustedBidPrice = Math.min(bid.getBidPrice(), currentBidMaximum - auction.getIncrement());
|
||||
int newBidPrice = adjustedBidPrice + auction.getIncrement();
|
||||
|
||||
return Effect().persist(Arrays.asList(
|
||||
new BidPlaced(entityUUID,
|
||||
new Bid(bid.getBidder(), now, adjustedBidPrice, bid.getBidPrice())
|
||||
),
|
||||
new BidPlaced(entityUUID,
|
||||
new Bid(currentBid.get().getBidder(), now, newBidPrice, currentBidMaximum)
|
||||
)
|
||||
))
|
||||
.thenReply(bid, newState -> new PlaceBidResult(PlaceBidStatus.ACCEPTED_OUTBID, newBidPrice, currentBid.get().getBidder()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the situation where a bid will be accepted as the new winning bidder.
|
||||
*/
|
||||
private Effect<AuctionEvent, AuctionState> handleNewWinningBidder(PlaceBid bid,
|
||||
Auction auction, Instant now, int currentBidMaximum) {
|
||||
int nextIncrement = Math.min(currentBidMaximum + auction.getIncrement(), bid.getBidPrice());
|
||||
int newBidPrice;
|
||||
if (nextIncrement < auction.getReservePrice()) {
|
||||
newBidPrice = Math.min(auction.getReservePrice(), bid.getBidPrice());
|
||||
} else {
|
||||
newBidPrice = nextIncrement;
|
||||
}
|
||||
return Effect().persist(new BidPlaced(
|
||||
entityUUID,
|
||||
new Bid(bid.getBidder(), now, newBidPrice, bid.getBidPrice())
|
||||
))
|
||||
.thenReply(bid, newState -> {
|
||||
PlaceBidStatus status;
|
||||
if (newBidPrice < auction.getReservePrice()) {
|
||||
status = PlaceBidStatus.ACCEPTED_BELOW_RESERVE;
|
||||
} else {
|
||||
status = PlaceBidStatus.ACCEPTED;
|
||||
}
|
||||
return new PlaceBidResult(status, newBidPrice, bid.getBidder());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuctionState emptyState() {
|
||||
return AuctionState.notStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandHandler<AuctionCommand, AuctionEvent, AuctionState> commandHandler() {
|
||||
return notStartedHandler
|
||||
.orElse(underAuctionHandler)
|
||||
.orElse(completedHandler)
|
||||
.orElse(getAuctionHandler)
|
||||
.orElse(cancelledHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventHandler<AuctionState, AuctionEvent> eventHandler() {
|
||||
return eventHandlerBuilder()
|
||||
.matchEvent(AuctionStarted.class, (state, evt) -> AuctionState.start(evt.getAuction()))
|
||||
.matchEvent(BidPlaced.class, (state, evt) -> state.bid(evt.getBid()))
|
||||
.matchEvent(BiddingFinished.class, (state, evt) -> state.withStatus(AuctionStatus.COMPLETE))
|
||||
.matchEvent(AuctionCancelled.class, (state, evt) -> state.withStatus(AuctionStatus.CANCELLED))
|
||||
.build();
|
||||
}
|
||||
|
||||
private PlaceBidResult createResult(AuctionState state, PlaceBidStatus status) {
|
||||
Optional<Bid> lastBid = state.lastBid();
|
||||
if (lastBid.isPresent()) {
|
||||
Bid bid = lastBid.get();
|
||||
return new PlaceBidResult(status, bid.getBidPrice(), bid.getBidder());
|
||||
} else {
|
||||
return new PlaceBidResult(status, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
private Effect<AuctionEvent, AuctionState> alreadyDone(ExpectingReply<Done> cmd) {
|
||||
return Effect().reply(cmd, Done.getInstance());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A persisted auction event.
|
||||
*/
|
||||
public interface AuctionEvent {
|
||||
|
||||
/**
|
||||
* The auction started.
|
||||
*/
|
||||
final class AuctionStarted implements AuctionEvent {
|
||||
|
||||
/**
|
||||
* The item that the auction started on.
|
||||
*/
|
||||
private final UUID itemId;
|
||||
/**
|
||||
* The auction details.
|
||||
*/
|
||||
private final Auction auction;
|
||||
|
||||
public AuctionStarted(UUID itemId, Auction auction) {
|
||||
this.itemId = itemId;
|
||||
this.auction = auction;
|
||||
}
|
||||
|
||||
public UUID getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public Auction getAuction() {
|
||||
return auction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A bid was placed.
|
||||
*/
|
||||
final class BidPlaced implements AuctionEvent {
|
||||
|
||||
/**
|
||||
* The item that the bid was placed on.
|
||||
*/
|
||||
private final UUID itemId;
|
||||
/**
|
||||
* The bid.
|
||||
*/
|
||||
private final Bid bid;
|
||||
|
||||
public BidPlaced(UUID itemId, Bid bid) {
|
||||
this.itemId = itemId;
|
||||
this.bid = bid;
|
||||
}
|
||||
|
||||
public UUID getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public Bid getBid() {
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bidding finished.
|
||||
*/
|
||||
final class BiddingFinished implements AuctionEvent {
|
||||
|
||||
/**
|
||||
* The item that bidding finished for.
|
||||
*/
|
||||
private final UUID itemId;
|
||||
|
||||
public BiddingFinished(UUID itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public UUID getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The auction was cancelled.
|
||||
*/
|
||||
final class AuctionCancelled implements AuctionEvent {
|
||||
|
||||
/**
|
||||
* The item that the auction was cancelled for.
|
||||
*/
|
||||
private final UUID itemId;
|
||||
|
||||
public AuctionCancelled(UUID itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public UUID getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The auction state.
|
||||
*/
|
||||
public final class AuctionState {
|
||||
|
||||
/**
|
||||
* The auction details.
|
||||
*/
|
||||
private final Optional<Auction> auction;
|
||||
/**
|
||||
* The status of the auction.
|
||||
*/
|
||||
private final AuctionStatus status;
|
||||
/**
|
||||
* The bidding history for the auction.
|
||||
*/
|
||||
private final List<Bid> biddingHistory;
|
||||
|
||||
public AuctionState(Optional<Auction> auction, AuctionStatus status, List<Bid> biddingHistory) {
|
||||
this.auction = auction;
|
||||
this.status = status;
|
||||
this.biddingHistory = biddingHistory;
|
||||
}
|
||||
|
||||
public static AuctionState notStarted() {
|
||||
return new AuctionState(Optional.empty(), AuctionStatus.NOT_STARTED, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static AuctionState start(Auction auction) {
|
||||
return new AuctionState(Optional.of(auction), AuctionStatus.UNDER_AUCTION, Collections.emptyList());
|
||||
}
|
||||
|
||||
public AuctionState withStatus(AuctionStatus status) {
|
||||
return new AuctionState(auction, status, biddingHistory);
|
||||
}
|
||||
|
||||
public AuctionState bid(Bid bid) {
|
||||
if (lastBid().filter(b -> b.getBidder().equals(bid.getBidder())).isPresent()) {
|
||||
// Current bidder has updated their bid
|
||||
List<Bid> newBiddingHistory = new ArrayList<>(biddingHistory);
|
||||
newBiddingHistory.remove(newBiddingHistory.size() - 1); // remove last
|
||||
newBiddingHistory.add(bid);
|
||||
return new AuctionState(auction, status, newBiddingHistory);
|
||||
} else {
|
||||
List<Bid> newBiddingHistory = new ArrayList<>(biddingHistory);
|
||||
newBiddingHistory.add(bid);
|
||||
return new AuctionState(auction, status, newBiddingHistory);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Bid> lastBid() {
|
||||
if (biddingHistory.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(biddingHistory.get(biddingHistory.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Auction> getAuction() {
|
||||
return auction;
|
||||
}
|
||||
|
||||
public AuctionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
/**
|
||||
* Auction status.
|
||||
*/
|
||||
public enum AuctionStatus {
|
||||
/**
|
||||
* The auction hasn't started yet (or doesn't exist).
|
||||
*/
|
||||
NOT_STARTED,
|
||||
/**
|
||||
* The item is under auction.
|
||||
*/
|
||||
UNDER_AUCTION,
|
||||
/**
|
||||
* The auction is complete.
|
||||
*/
|
||||
COMPLETE,
|
||||
/**
|
||||
* The auction is cancelled.
|
||||
*/
|
||||
CANCELLED
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A bid.
|
||||
*/
|
||||
public final class Bid {
|
||||
/**
|
||||
* The bidder.
|
||||
*/
|
||||
private final UUID bidder;
|
||||
/**
|
||||
* The time the bid was placed.
|
||||
*/
|
||||
private final Instant bidTime;
|
||||
/**
|
||||
* The bid price.
|
||||
*/
|
||||
private final int bidPrice;
|
||||
/**
|
||||
* The maximum the bidder is willing to bid.
|
||||
*/
|
||||
private final int maximumBid;
|
||||
|
||||
public Bid(UUID bidder, Instant bidTime, int bidPrice, int maximumBid) {
|
||||
this.bidder = bidder;
|
||||
this.bidTime = bidTime;
|
||||
this.bidPrice = bidPrice;
|
||||
this.maximumBid = maximumBid;
|
||||
}
|
||||
|
||||
public UUID getBidder() {
|
||||
return bidder;
|
||||
}
|
||||
|
||||
public Instant getBidTime() {
|
||||
return bidTime;
|
||||
}
|
||||
|
||||
public int getBidPrice() {
|
||||
return bidPrice;
|
||||
}
|
||||
|
||||
public int getMaximumBid() {
|
||||
return maximumBid;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package jdocs.akka.persistence.typed.auction;
|
||||
|
||||
/**
|
||||
* The status of the result of placing a bid.
|
||||
*/
|
||||
public enum BidResultStatus {
|
||||
/**
|
||||
* The bid was accepted, and is the current highest bid.
|
||||
*/
|
||||
ACCEPTED,
|
||||
/**
|
||||
* The bid was accepted, but was outbidded by the maximum bid of the current highest bidder.
|
||||
*/
|
||||
ACCEPTED_OUTBID,
|
||||
/**
|
||||
* The bid was accepted, but is below the reserve.
|
||||
*/
|
||||
ACCEPTED_BELOW_RESERVE,
|
||||
/**
|
||||
* The bid was not at least the current bid plus the increment.
|
||||
*/
|
||||
TOO_LOW,
|
||||
/**
|
||||
* The auction hasn't started.
|
||||
*/
|
||||
NOT_STARTED,
|
||||
/**
|
||||
* The auction has already finished.
|
||||
*/
|
||||
FINISHED,
|
||||
/**
|
||||
* The auction has been cancelled.
|
||||
*/
|
||||
CANCELLED
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue