From 07a7a1cb3e4cfc1580bc05b0a6010a1e9f7e7508 Mon Sep 17 00:00:00 2001 From: Kostya Date: Sun, 16 Aug 2026 11:55:08 +0000 Subject: Rename Option/Account to Market/Ledger, add Result-based error handling Split GuessMarketContext into a separate CatalogContext for read-only event listing, wrap fallible operations in Result instead of throwing, and wire load/save of events and users through a JSON Wrapper. --- .../market/guess/service/LocalCatalogContext.java | 45 +++++++++++++ .../guess/service/LocalGuessMarketContext.java | 61 +++++++----------- .../market/guess/service/TradingOperation.java | 4 +- .../market/guess/service/TradingOperationImpl.java | 4 +- .../java/market/guess/service/domain/Account.java | 49 --------------- .../java/market/guess/service/domain/Event.java | 73 +++++++++++++++------- .../market/guess/service/domain/LedgerEntry.java | 13 ---- .../java/market/guess/service/domain/Market.java | 32 ++++++++++ .../java/market/guess/service/domain/Option.java | 3 - .../java/market/guess/service/domain/User.java | 7 ++- .../service/infrastructure/MarketContext.java | 34 +++++++++- .../infrastructure/mapper/v1/EventMapperV1.java | 8 +-- .../infrastructure/mapper/v2/EventMapperV2.java | 10 +-- .../repository/InMemoryEventRepository.java | 5 ++ .../repository/InMemoryUserRepository.java | 5 ++ .../infrastructure/repository/Repository.java | 2 + .../java/market/guess/service/ledger/Ledger.java | 35 +++++++++++ .../market/guess/service/ledger/LedgerContext.java | 49 +++++++++++++++ .../market/guess/service/ledger/LedgerEntry.java | 13 ++++ 19 files changed, 307 insertions(+), 145 deletions(-) create mode 100644 service/src/main/java/market/guess/service/LocalCatalogContext.java delete mode 100644 service/src/main/java/market/guess/service/domain/Account.java delete mode 100644 service/src/main/java/market/guess/service/domain/LedgerEntry.java create mode 100644 service/src/main/java/market/guess/service/domain/Market.java delete mode 100644 service/src/main/java/market/guess/service/domain/Option.java create mode 100644 service/src/main/java/market/guess/service/ledger/Ledger.java create mode 100644 service/src/main/java/market/guess/service/ledger/LedgerContext.java create mode 100644 service/src/main/java/market/guess/service/ledger/LedgerEntry.java (limited to 'service/src/main') diff --git a/service/src/main/java/market/guess/service/LocalCatalogContext.java b/service/src/main/java/market/guess/service/LocalCatalogContext.java new file mode 100644 index 0000000..15a5890 --- /dev/null +++ b/service/src/main/java/market/guess/service/LocalCatalogContext.java @@ -0,0 +1,45 @@ +package market.guess.service; + +import java.nio.file.Path; +import java.util.List; +import market.guess.api.CatalogContext; +import market.guess.api.EventDetailDTO; +import market.guess.api.EventSummaryDTO; +import market.guess.api.LoadResultDTO; +import market.guess.api.Result; +import market.guess.service.domain.Event; +import market.guess.service.infrastructure.MarketContext; +import market.guess.service.infrastructure.provider.Loader; + +public final class LocalCatalogContext implements CatalogContext { + + private final MarketContext context; + private final Loader provider; + + public LocalCatalogContext(Loader provider, MarketContext context) { + super(); + this.provider = provider; + this.context = context; + } + + @Override + public Result loadEvents(Path path) { + return Result.ok(provider.load(path)); + } + + @Override + public Result> getAllEvents() { + return Result.ok(context.getEvents().getAll().stream().map(Event::toEventSummary).toList()); + } + + @Override + public Result getEvent(String eventKey) { + var optional = context.getEvents().get(eventKey); + + if (optional.isEmpty()) return Result.error("Unable to find event."); + var event = optional.get(); + + return Result.ok( + new EventDetailDTO(event.toEventSummary(), event.toMarketState(), event.getHistory(), "")); + } +} diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java index b405329..8f97432 100644 --- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java +++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java @@ -4,83 +4,66 @@ import java.nio.file.Path; import java.util.List; import market.guess.api.AccountDTO; import market.guess.api.EventDetailDTO; -import market.guess.api.EventSummaryDTO; import market.guess.api.GuessMarketContext; import market.guess.api.LoadResultDTO; +import market.guess.api.ProblemDTO; import market.guess.api.PurchaseReceiptDTO; -import market.guess.exception.GuessMarketException; -import market.guess.service.domain.Event; +import market.guess.api.Result; import market.guess.service.infrastructure.MarketContext; -import market.guess.service.infrastructure.provider.Loader; public final class LocalGuessMarketContext implements GuessMarketContext { private final TradingOperation tradingOperation; private final MarketContext context; - private final Loader provider; - public LocalGuessMarketContext( - Loader provider, MarketContext context, TradingOperation tradingOperation) { + public LocalGuessMarketContext(MarketContext context, TradingOperation tradingOperation) { super(); - this.provider = provider; this.context = context; this.tradingOperation = tradingOperation; } @Override - public LoadResultDTO loadEvents(Path path) { - return provider.load(path); - } - - @Override - public List listEvents() { - return context.getEvents().getAll().stream().map(Event::toEventSummary).toList(); - } - - @Override - public boolean isLoaded() { - throw new UnsupportedOperationException("Unimplemented method 'isLoaded'"); - } - - @Override - public EventDetailDTO getEvent(String eventKey) { - throw new UnsupportedOperationException("Unimplemented method 'getEvent'"); - } - - @Override - public PurchaseReceiptDTO buyShares( - String userName, String eventKey, String optionKey, int quantity) - throws GuessMarketException { + public Result buyShares( + String userName, String eventKey, String optionKey, int quantity) { var optional = context.getEvents().get(eventKey); var userOptional = context.getUsers().get(userName); - if (optional.isEmpty() || userOptional.isEmpty()) throw new GuessMarketException(); + if (optional.isEmpty() || userOptional.isEmpty()) Result.error("Unable to find user/event."); var event = optional.get(); var user = userOptional.get(); var option = event.getOption(optionKey); var trade = tradingOperation.buy(event, user, option, quantity); - return new PurchaseReceiptDTO(); + return Result.ok(new PurchaseReceiptDTO()); } @Override - public EventDetailDTO closeEvent(String userName, String eventKey, String winningOptionKey) - throws GuessMarketException { + public Result closeEvent( + String userName, String eventKey, String winningOptionKey) { throw new UnsupportedOperationException("Unimplemented method 'account'"); } @Override - public AccountDTO account(String userName) { + public Result account(String userName) { throw new UnsupportedOperationException("Unimplemented method 'account'"); } @Override - public void saveState(Path path) throws GuessMarketException { + public Result saveState(Path path) { context.save(); + return Result.ok(); } @Override - public LoadResultDTO restoreState(Path path) { - throw new UnsupportedOperationException("Unimplemented method 'restoreState'"); + public Result restoreState(Path path) { + + try { + context.load(); + } catch (Exception e) { + return Result.error( + "Failed to load.", + LoadResultDTO.failure(path.toString(), List.of(new ProblemDTO("", e.getMessage(), "")))); + } + return Result.ok(LoadResultDTO.success(path.toString(), context.getEvents().getAll().size())); } } diff --git a/service/src/main/java/market/guess/service/TradingOperation.java b/service/src/main/java/market/guess/service/TradingOperation.java index c8153c2..63572c0 100644 --- a/service/src/main/java/market/guess/service/TradingOperation.java +++ b/service/src/main/java/market/guess/service/TradingOperation.java @@ -1,13 +1,13 @@ package market.guess.service; import market.guess.service.domain.Event; -import market.guess.service.domain.Option; +import market.guess.service.domain.Market; import market.guess.service.domain.Settlement; import market.guess.service.domain.Trade; import market.guess.service.domain.User; public interface TradingOperation { - Trade buy(Event event, User user, Option Option, int quantity); + Trade buy(Event event, User user, Market Option, int quantity); Settlement settle(Event event, String winningOption); } diff --git a/service/src/main/java/market/guess/service/TradingOperationImpl.java b/service/src/main/java/market/guess/service/TradingOperationImpl.java index f6c9aa1..641a3a9 100644 --- a/service/src/main/java/market/guess/service/TradingOperationImpl.java +++ b/service/src/main/java/market/guess/service/TradingOperationImpl.java @@ -1,7 +1,7 @@ package market.guess.service; import market.guess.service.domain.Event; -import market.guess.service.domain.Option; +import market.guess.service.domain.Market; import market.guess.service.domain.Settlement; import market.guess.service.domain.Trade; import market.guess.service.domain.User; @@ -9,7 +9,7 @@ import market.guess.service.domain.User; public final class TradingOperationImpl implements TradingOperation { @Override - public Trade buy(Event event, User user, Option Option, int quantity) { + public Trade buy(Event event, User user, Market Option, int quantity) { // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'buy'"); } diff --git a/service/src/main/java/market/guess/service/domain/Account.java b/service/src/main/java/market/guess/service/domain/Account.java deleted file mode 100644 index 48d5054..0000000 --- a/service/src/main/java/market/guess/service/domain/Account.java +++ /dev/null @@ -1,49 +0,0 @@ -package market.guess.service.domain; - -import java.math.BigDecimal; -import java.time.Clock; -import java.util.ArrayList; -import java.util.List; -import market.guess.api.LedgerType; - -public final class Account { - private final String owner; - private final List entries = new ArrayList<>(); - private BigDecimal balance; - private int runningId; - - private final Clock clock; - - public Account(String owner, BigDecimal initialBalance) { - this(owner, initialBalance, Clock.systemUTC()); - } - - public Account(String owner, BigDecimal initialBalance, Clock clock) { - super(); - this.owner = owner; - this.balance = BigDecimalOptions.toMoney(initialBalance); - this.clock = clock; - } - - public String getOwner() { - return owner; - } - - public BigDecimal getBalance() { - return balance; - } - - public boolean credit(LedgerType type, BigDecimal amount, String note) { - record(type, amount, note); - return true; - } - - public boolean debit(LedgerType type, BigDecimal amount, String note) { - record(type, amount.negate(), note); - return true; - } - - private void record(LedgerType type, BigDecimal amount, String note) { - entries.add(new LedgerEntry(runningId++, clock.instant(), type, amount, balance, note)); - } -} diff --git a/service/src/main/java/market/guess/service/domain/Event.java b/service/src/main/java/market/guess/service/domain/Event.java index ce35e82..5c98908 100644 --- a/service/src/main/java/market/guess/service/domain/Event.java +++ b/service/src/main/java/market/guess/service/domain/Event.java @@ -7,6 +7,9 @@ import java.util.List; import market.guess.api.CommissionTiming; import market.guess.api.EventStatus; import market.guess.api.EventSummaryDTO; +import market.guess.api.MarketStateDTO; +import market.guess.api.TradeRowDTO; +import market.guess.service.ledger.Ledger; import market.guess.service.mechanism.TradingMechanism; public final class Event { @@ -17,13 +20,13 @@ public final class Event { private final int commissionPercent; private final CommissionTiming commissionTiming; private final TradingMechanism mechanism; - private final List