This commit is contained in:
Stefan
2022-06-17 17:47:49 +02:00
parent 378fb7cffa
commit 332ee608cf
36 changed files with 128 additions and 133 deletions

View File

@@ -1,6 +1,4 @@
# Bux backend assigment
## Candidate: Stefan Dragisic
# Reactive Stock Exchange
### Introduction

View File

@@ -10,7 +10,7 @@ Content-Type: application/json
"asset": "BTC",
"price": 43251.00,
"amount": 2.0,
"direction": "BUY"
"direction": "SELL"
}
#### Get an order

View File

@@ -9,11 +9,11 @@
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>io.bux</groupId>
<artifactId>backend-assignment-starter-matching-engine</artifactId>
<groupId>com.github.schananas</groupId>
<artifactId>reactivestockexhange</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend-assignment-starter-matching-engine</name>
<description>Simple matching engine</description>
<name>Reactive Stock Exchange</name>
<description>Proof of concept for reactive implementation of reactive stock exchange with only a simple limit order matching engine.</description>
<properties>
<java.version>17</java.version>
</properties>

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine;
package com.github.schananas.reactivestockexchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -9,7 +9,7 @@ import org.springframework.web.client.RestTemplate;
import java.util.List;
@SpringBootApplication
public class MatchingEngineApplication {
public class ReactiveStockExchangeApplication {
@Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
@@ -22,7 +22,7 @@ public class MatchingEngineApplication {
}
public static void main(String[] args) {
SpringApplication.run(MatchingEngineApplication.class, args);
SpringApplication.run(ReactiveStockExchangeApplication.class, args);
}
}

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
import reactor.core.publisher.Mono;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
import java.util.UUID;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
/**
* Interface to define event

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
import reactor.core.publisher.Mono;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
/**
* Interface to define sourcing event - event that changes aggregate and projection state

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
/**
* Interface to define update event - event that changes only projection state

View File

@@ -1,15 +1,15 @@
package io.bux.matchingengine.domain;
package com.github.schananas.reactivestockexchange.domain;
import io.bux.matchingengine.cqrs.Aggregate;
import io.bux.matchingengine.cqrs.Command;
import io.bux.matchingengine.cqrs.Event;
import io.bux.matchingengine.cqrs.SourcingEvent;
import io.bux.matchingengine.domain.command.CancelOrderCommand;
import io.bux.matchingengine.domain.command.MakeOrderCommand;
import io.bux.matchingengine.domain.engine.MatchingEngine;
import io.bux.matchingengine.domain.events.CancellationRequestedEvent;
import io.bux.matchingengine.domain.events.OrderAcceptedEvent;
import io.bux.matchingengine.domain.events.OrderRejectedEvent;
import com.github.schananas.reactivestockexchange.cqrs.Aggregate;
import com.github.schananas.reactivestockexchange.cqrs.Command;
import com.github.schananas.reactivestockexchange.cqrs.Event;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.domain.command.CancelOrderCommand;
import com.github.schananas.reactivestockexchange.domain.command.MakeOrderCommand;
import com.github.schananas.reactivestockexchange.domain.engine.MatchingEngine;
import com.github.schananas.reactivestockexchange.domain.events.CancellationRequestedEvent;
import com.github.schananas.reactivestockexchange.domain.events.OrderAcceptedEvent;
import com.github.schananas.reactivestockexchange.domain.events.OrderRejectedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;

View File

@@ -1,8 +1,7 @@
package io.bux.matchingengine.domain;
package com.github.schananas.reactivestockexchange.domain;
import io.bux.matchingengine.cqrs.AggregateRepository;
import io.bux.matchingengine.domain.Book;
import io.bux.matchingengine.domain.query.BookQueryRepository;
import com.github.schananas.reactivestockexchange.cqrs.AggregateRepository;
import com.github.schananas.reactivestockexchange.domain.query.BookQueryRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

View File

@@ -1,8 +1,8 @@
package io.bux.matchingengine.domain.bus;
package com.github.schananas.reactivestockexchange.domain.bus;
import io.bux.matchingengine.cqrs.Command;
import io.bux.matchingengine.cqrs.SourcingEvent;
import io.bux.matchingengine.domain.BookAggregateRepository;
import com.github.schananas.reactivestockexchange.cqrs.Command;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.domain.BookAggregateRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@@ -11,7 +11,6 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.core.scheduler.Schedulers;
import java.time.Duration;
import java.util.function.Consumer;
import javax.annotation.PreDestroy;

View File

@@ -1,6 +1,6 @@
package io.bux.matchingengine.domain.command;
package com.github.schananas.reactivestockexchange.domain.command;
import io.bux.matchingengine.cqrs.Command;
import com.github.schananas.reactivestockexchange.cqrs.Command;
import org.springframework.lang.NonNull;
import java.math.BigDecimal;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.command;
package com.github.schananas.reactivestockexchange.domain.command;
import io.bux.matchingengine.cqrs.Command;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.Command;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.springframework.lang.NonNull;
import java.math.BigDecimal;

View File

@@ -1,10 +1,10 @@
package io.bux.matchingengine.domain.engine;
package com.github.schananas.reactivestockexchange.domain.engine;
import io.bux.matchingengine.cqrs.UpdateEvent;
import io.bux.matchingengine.domain.query.OrderType;
import io.bux.matchingengine.domain.engine.events.OrderCanceledEvent;
import io.bux.matchingengine.domain.engine.events.OrderMatchedEvent;
import io.bux.matchingengine.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderCanceledEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderMatchedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.cqrs.UpdateEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;

View File

@@ -1,6 +1,6 @@
package io.bux.matchingengine.domain.engine;
package com.github.schananas.reactivestockexchange.domain.engine;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import java.math.BigDecimal;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.engine.events;
package com.github.schananas.reactivestockexchange.domain.engine.events;
import io.bux.matchingengine.cqrs.UpdateEvent;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.UpdateEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import java.math.BigDecimal;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.engine.events;
package com.github.schananas.reactivestockexchange.domain.engine.events;
import io.bux.matchingengine.cqrs.UpdateEvent;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.UpdateEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import java.math.BigDecimal;
import java.time.Instant;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.engine.events;
package com.github.schananas.reactivestockexchange.domain.engine.events;
import io.bux.matchingengine.cqrs.UpdateEvent;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.UpdateEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import java.math.BigDecimal;
import java.time.Instant;

View File

@@ -1,6 +1,6 @@
package io.bux.matchingengine.domain.events;
package com.github.schananas.reactivestockexchange.domain.events;
import io.bux.matchingengine.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import org.springframework.lang.NonNull;
import java.math.BigDecimal;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.events;
package com.github.schananas.reactivestockexchange.domain.events;
import io.bux.matchingengine.cqrs.SourcingEvent;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.springframework.lang.NonNull;
import java.math.BigDecimal;

View File

@@ -1,7 +1,7 @@
package io.bux.matchingengine.domain.events;
package com.github.schananas.reactivestockexchange.domain.events;
import io.bux.matchingengine.cqrs.SourcingEvent;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.springframework.lang.NonNull;
import java.math.BigDecimal;

View File

@@ -1,10 +1,10 @@
package io.bux.matchingengine.domain.query;
package com.github.schananas.reactivestockexchange.domain.query;
import io.bux.matchingengine.cqrs.Event;
import io.bux.matchingengine.cqrs.QueryRepository;
import io.bux.matchingengine.domain.engine.events.OrderCanceledEvent;
import io.bux.matchingengine.domain.engine.events.OrderMatchedEvent;
import io.bux.matchingengine.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderCanceledEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderMatchedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.cqrs.Event;
import com.github.schananas.reactivestockexchange.cqrs.QueryRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.domain.query;
package com.github.schananas.reactivestockexchange.domain.query;
import java.math.BigDecimal;
import java.time.Instant;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.domain.query;
package com.github.schananas.reactivestockexchange.domain.query;
import java.math.BigDecimal;

View File

@@ -1,4 +1,4 @@
package io.bux.matchingengine.domain.query;
package com.github.schananas.reactivestockexchange.domain.query;
/**
* @author Stefan Dragisic

View File

@@ -1,19 +1,19 @@
package io.bux.matchingengine.web;
package com.github.schananas.reactivestockexchange.web;
import io.bux.matchingengine.api.protobuf.OrderStatusResponse;
import io.bux.matchingengine.api.protobuf.PlaceOrderRequest;
import io.bux.matchingengine.api.protobuf.Trade;
import io.bux.matchingengine.cqrs.Event;
import io.bux.matchingengine.cqrs.SourcingEvent;
import io.bux.matchingengine.domain.Book;
import io.bux.matchingengine.domain.BookAggregateRepository;
import io.bux.matchingengine.domain.bus.CommandBus;
import io.bux.matchingengine.domain.command.CancelOrderCommand;
import io.bux.matchingengine.domain.command.MakeOrderCommand;
import io.bux.matchingengine.domain.events.OrderAcceptedEvent;
import io.bux.matchingengine.domain.query.BookQueryRepository;
import io.bux.matchingengine.domain.query.OrderEntry;
import io.bux.matchingengine.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.api.protobuf.OrderStatusResponse;
import com.github.schananas.reactivestockexchange.api.protobuf.PlaceOrderRequest;
import com.github.schananas.reactivestockexchange.api.protobuf.Trade;
import com.github.schananas.reactivestockexchange.cqrs.Event;
import com.github.schananas.reactivestockexchange.cqrs.SourcingEvent;
import com.github.schananas.reactivestockexchange.domain.bus.CommandBus;
import com.github.schananas.reactivestockexchange.domain.command.CancelOrderCommand;
import com.github.schananas.reactivestockexchange.domain.command.MakeOrderCommand;
import com.github.schananas.reactivestockexchange.domain.events.OrderAcceptedEvent;
import com.github.schananas.reactivestockexchange.domain.query.BookQueryRepository;
import com.github.schananas.reactivestockexchange.domain.query.OrderEntry;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.domain.Book;
import com.github.schananas.reactivestockexchange.domain.BookAggregateRepository;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -33,18 +33,17 @@ import java.util.stream.Collectors;
* Implements REST Endpoints to place, get or cancel order.
*
* @author Stefan Dragisic
* @author bux
*/
@RestController
public class TradingController {
public class MarketController {
private final CommandBus commandBus;
private final BookAggregateRepository bookAggregateRepository;
private final BookQueryRepository bookQueryRepository;
public TradingController(CommandBus commandBus,
BookAggregateRepository bookAggregateRepository,
BookQueryRepository bookQueryRepository) {
public MarketController(CommandBus commandBus,
BookAggregateRepository bookAggregateRepository,
BookQueryRepository bookQueryRepository) {
this.commandBus = commandBus;
this.bookAggregateRepository = bookAggregateRepository;
this.bookQueryRepository = bookQueryRepository;
@@ -98,7 +97,7 @@ public class TradingController {
@PostMapping("/orders/{orderId}/cancel")
public Mono<ResponseEntity<String>> cancelOrder(@PathVariable Long orderId) {
return bookQueryRepository.getOrder(orderId)
.flatMap(TradingController::validateOrderAmount)
.flatMap(MarketController::validateOrderAmount)
.flatMap(this::sendCancelCommand)
.switchIfEmpty(Mono.error(new IllegalStateException(
"You can't cancel non-existing order.")))
@@ -135,7 +134,7 @@ public class TradingController {
.setAsset(order.asset())
.setAmount(order.amount().doubleValue())
.setPrice(order.price().doubleValue())
.setDirection(io.bux.matchingengine.api.protobuf.OrderType.valueOf(
.setDirection(com.github.schananas.reactivestockexchange.api.protobuf.OrderType.valueOf(
order.direction().name()))
.addAllTrades(order.trades().stream()
.map(t -> Trade.newBuilder()

View File

@@ -1,8 +1,8 @@
syntax = "proto3";
package io.bux.matchingengine.api;
package com.github.schananas.reactivestockexchange.api;
option java_package = "io.bux.matchingengine.api.protobuf";
option java_package = "com.github.schananas.reactivestockexchange.api.protobuf";
option java_multiple_files = true;
enum OrderType {

View File

@@ -1,14 +1,14 @@
package io.bux.matchingengine.cqrs;
package com.github.schananas.reactivestockexchange.cqrs;
import io.bux.matchingengine.domain.Book;
import io.bux.matchingengine.domain.BookAggregateRepository;
import io.bux.matchingengine.domain.query.OrderType;
import io.bux.matchingengine.domain.command.CancelOrderCommand;
import io.bux.matchingengine.domain.command.MakeOrderCommand;
import io.bux.matchingengine.domain.bus.CommandBus;
import io.bux.matchingengine.domain.engine.MatchingEngine;
import io.bux.matchingengine.domain.events.CancellationRequestedEvent;
import io.bux.matchingengine.domain.events.OrderAcceptedEvent;
import com.github.schananas.reactivestockexchange.domain.bus.CommandBus;
import com.github.schananas.reactivestockexchange.domain.command.CancelOrderCommand;
import com.github.schananas.reactivestockexchange.domain.command.MakeOrderCommand;
import com.github.schananas.reactivestockexchange.domain.engine.MatchingEngine;
import com.github.schananas.reactivestockexchange.domain.events.CancellationRequestedEvent;
import com.github.schananas.reactivestockexchange.domain.events.OrderAcceptedEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import com.github.schananas.reactivestockexchange.domain.Book;
import com.github.schananas.reactivestockexchange.domain.BookAggregateRepository;
import org.junit.jupiter.api.*;
import org.mockito.*;
import reactor.core.publisher.Mono;

View File

@@ -1,6 +1,6 @@
package io.bux.matchingengine.domain;
package com.github.schananas.reactivestockexchange.domain;
import io.bux.matchingengine.domain.query.BookQueryRepository;
import com.github.schananas.reactivestockexchange.domain.query.BookQueryRepository;
import org.junit.jupiter.api.*;
import reactor.test.StepVerifier;

View File

@@ -1,9 +1,9 @@
package io.bux.matchingengine.domain;
package com.github.schananas.reactivestockexchange.domain;
import io.bux.matchingengine.domain.query.OrderType;
import io.bux.matchingengine.domain.query.BookQueryRepository;
import io.bux.matchingengine.domain.engine.events.OrderMatchedEvent;
import io.bux.matchingengine.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderMatchedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.query.BookQueryRepository;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.junit.jupiter.api.*;
import reactor.test.StepVerifier;
@@ -42,7 +42,7 @@ class BookQueryRepositoryTest {
@Test
void buxTest() {
void stockTest() {
StepVerifier.create(testSubject.updateProjection(new OrderPlacedEvent(0L,
"BTC",
Instant.MIN,

View File

@@ -1,10 +1,10 @@
package io.bux.matchingengine.domain.engine;
package com.github.schananas.reactivestockexchange.domain.engine;
import io.bux.matchingengine.domain.query.OrderType;
import io.bux.matchingengine.domain.engine.events.OrderCanceledEvent;
import io.bux.matchingengine.domain.engine.events.OrderMatchedEvent;
import io.bux.matchingengine.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderCanceledEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderMatchedEvent;
import com.github.schananas.reactivestockexchange.domain.engine.events.OrderPlacedEvent;
import com.github.schananas.reactivestockexchange.domain.query.OrderType;
import org.junit.jupiter.api.*;
import reactor.test.StepVerifier;
@@ -19,7 +19,7 @@ class MatchingEngineTest {
private final MatchingEngine testSubject = new MatchingEngine();
@Test
public void buxTest() {
public void stockTest() {
StepVerifier.create(testSubject.engineEvents().take(9))
.expectSubscription()
.then(() -> testSubject.placeOrder(1,
@@ -78,7 +78,7 @@ class MatchingEngineTest {
}
@Test
public void buxTest2() {
public void stockTest2() {
StepVerifier.create(testSubject.engineEvents().take(4))
.expectSubscription()
.then(() -> testSubject.placeOrder(0L,

View File

@@ -1,8 +1,8 @@
package io.bux.matchingengine.integration;
package com.github.schananas.reactivestockexchange.integration;
import io.bux.matchingengine.MatchingEngineApplication;
import io.bux.matchingengine.api.protobuf.OrderStatusResponse;
import io.bux.matchingengine.api.protobuf.OrderType;
import com.github.schananas.reactivestockexchange.api.protobuf.OrderStatusResponse;
import com.github.schananas.reactivestockexchange.api.protobuf.OrderType;
import com.github.schananas.reactivestockexchange.ReactiveStockExchangeApplication;
import org.junit.*;
import org.junit.runner.*;
import org.slf4j.Logger;
@@ -25,7 +25,7 @@ import java.util.concurrent.atomic.AtomicLong;
* @author Stefan Dragisic
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MatchingEngineApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringBootTest(classes = ReactiveStockExchangeApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTest {
private final Logger logger = LoggerFactory.getLogger(IntegrationTest.class);
@@ -65,7 +65,7 @@ public class IntegrationTest {
""".stripIndent();
private final String SOL_SELL_ORDER = """
{
"asset": "BTC",
"asset": "SOL",
"price": 40000.00,
"amount": 1.0,
"direction": "SELL"
@@ -73,7 +73,7 @@ public class IntegrationTest {
""".stripIndent();
private final String SOL_BUY_ORDER = """
{
"asset": "BTC",
"asset": "SOL",
"price": 40000.00,
"amount": 1,
"direction": "BUY"

BIN
system_requirements.pdf Normal file

Binary file not shown.