From b1b959df5001231158cfe43dbdf23729634b21a2 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 8 Oct 2018 19:36:29 +0200 Subject: [PATCH] AuctionEntity example in Java, #25485 --- .../persistence/typed/auction/Auction.java | 71 ++++++ .../typed/auction/AuctionCommand.java | 236 +++++++++++++++++ .../typed/auction/AuctionEntity.java | 241 ++++++++++++++++++ .../typed/auction/AuctionEvent.java | 108 ++++++++ .../typed/auction/AuctionState.java | 77 ++++++ .../typed/auction/AuctionStatus.java | 27 ++ .../akka/persistence/typed/auction/Bid.java | 53 ++++ .../typed/auction/BidResultStatus.java | 39 +++ 8 files changed, 852 insertions(+) create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Auction.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionCommand.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEntity.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEvent.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionState.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionStatus.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Bid.java create mode 100644 akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/BidResultStatus.java diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Auction.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Auction.java new file mode 100644 index 0000000000..86ad9900f6 --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Auction.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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; + } +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionCommand.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionCommand.java new file mode 100644 index 0000000000..bdf884e1aa --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionCommand.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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 { + + /** + * The auction to start. + */ + private final Auction auction; + + private final ActorRef replyTo; + + public StartAuction(Auction auction, ActorRef replyTo) { + this.auction = auction; + this.replyTo = replyTo; + } + + @Override + public ActorRef replyTo() { + return replyTo; + } + + public Auction getAuction() { + return auction; + } + } + + /** + * Cancel the auction. + */ + final class CancelAuction implements AuctionCommand, ExpectingReply { + private final ActorRef replyTo; + + public CancelAuction(ActorRef replyTo) { + this.replyTo = replyTo; + } + + @Override + public ActorRef replyTo() { + return replyTo; + } + + } + + /** + * Place a bid on the auction. + */ + final class PlaceBid implements AuctionCommand, ExpectingReply { + + private final int bidPrice; + private final UUID bidder; + + private final ActorRef replyTo; + + public PlaceBid(int bidPrice, UUID bidder, ActorRef replyTo) { + this.bidPrice = bidPrice; + this.bidder = bidder; + this.replyTo = replyTo; + } + + @Override + public ActorRef 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 { + + private final ActorRef replyTo; + + FinishBidding(ActorRef replyTo) { + this.replyTo = replyTo; + } + + @Override + public ActorRef replyTo() { + return replyTo; + } + } + + /** + * Get the auction. + */ + final class GetAuction implements AuctionCommand, ExpectingReply { + private final ActorRef replyTo; + + public GetAuction(ActorRef replyTo) { + this.replyTo = replyTo; + } + + @Override + public ActorRef replyTo() { + return replyTo; + } + } +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEntity.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEntity.java new file mode 100644 index 0000000000..cd6fac90fb --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEntity.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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 { + + 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 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 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 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 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 getAuctionHandler = + commandHandlerBuilder(AuctionState.class) + .matchCommand(GetAuction.class, (state, cmd) -> Effect().reply(cmd, state)); + + private CommandHandlerBuilder 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 startAuction(AuctionState state, StartAuction cmd) { + return Effect().persist(new AuctionStarted(entityUUID, cmd.getAuction())) + .thenReply(cmd, __ -> Done.getInstance()); + } + + private Effect finishBidding(AuctionState state, FinishBidding cmd) { + return Effect().persist(new BiddingFinished(entityUUID)) + .thenReply(cmd, __ -> Done.getInstance()); + } + + private Effect cancelAuction(AuctionState state, CancelAuction cmd) { + return Effect().persist(new AuctionCancelled(entityUUID)) + .thenReply(cmd, __ -> Done.getInstance()); + } + + /** + * The main logic for handling of bids. + */ + private Effect 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 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 handleAutomaticOutbid( + PlaceBid bid, Auction auction, Instant now, Optional 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 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 commandHandler() { + return notStartedHandler + .orElse(underAuctionHandler) + .orElse(completedHandler) + .orElse(getAuctionHandler) + .orElse(cancelledHandler) + .build(); + } + + @Override + public EventHandler 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 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 alreadyDone(ExpectingReply cmd) { + return Effect().reply(cmd, Done.getInstance()); + } +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEvent.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEvent.java new file mode 100644 index 0000000000..bd058aa231 --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionEvent.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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; + } + } + +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionState.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionState.java new file mode 100644 index 0000000000..14f98a5db5 --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionState.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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; + /** + * The status of the auction. + */ + private final AuctionStatus status; + /** + * The bidding history for the auction. + */ + private final List biddingHistory; + + public AuctionState(Optional auction, AuctionStatus status, List 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 newBiddingHistory = new ArrayList<>(biddingHistory); + newBiddingHistory.remove(newBiddingHistory.size() - 1); // remove last + newBiddingHistory.add(bid); + return new AuctionState(auction, status, newBiddingHistory); + } else { + List newBiddingHistory = new ArrayList<>(biddingHistory); + newBiddingHistory.add(bid); + return new AuctionState(auction, status, newBiddingHistory); + } + } + + public Optional lastBid() { + if (biddingHistory.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(biddingHistory.get(biddingHistory.size() - 1)); + } + } + + public Optional getAuction() { + return auction; + } + + public AuctionStatus getStatus() { + return status; + } +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionStatus.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionStatus.java new file mode 100644 index 0000000000..40fa0f3ce0 --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/AuctionStatus.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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 +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Bid.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Bid.java new file mode 100644 index 0000000000..8e7173253b --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/Bid.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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; + } +} diff --git a/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/BidResultStatus.java b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/BidResultStatus.java new file mode 100644 index 0000000000..3cbe42aea1 --- /dev/null +++ b/akka-persistence-typed/src/test/java/jdocs/akka/persistence/typed/auction/BidResultStatus.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ + +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 +}