public interface CommandBus {

  void dispatch(Command command);

  <T extends CommandHandler> T register(T handler);

} public interface Repository {

  <AR extends AggregateRoot> void save(AR aggregateRoot);

  <AR extends AggregateRoot, ID extends GenericId> AR load(ID id, Class<AR> aggregateType);

} public interface DomainEventBus {

  void publish(List<DomainEvent> events);

  void republish(List<DomainEvent> events);

  <T extends DomainEventListener> T register(T listener);

} public interface DomainEventStore {

  List<DomainEvent> loadEvents(GenericId id);

  void save(GenericId id, Class<? extends AggregateRoot> type, List<DomainEvent> events);

  List<DomainEvent> getAllEvents();

} public class DefaultRepository implements Repository {

  private final DomainEventBus domainEventBus;
  private final DomainEventStore domainEventStore;

  public DefaultRepository(DomainEventBus domainEventBus, DomainEventStore domainEventStore) {
    this.domainEventBus = domainEventBus;
    this.domainEventStore = domainEventStore;
  }

  @Override
  public <AR extends AggregateRoot> void save(AR aggregateRoot) {
    if (aggregateRoot.hasUncommittedEvents()) {
      List<DomainEvent> newEvents = aggregateRoot.getUncommittedEvents();
      domainEventStore.save(aggregateRoot.id(), aggregateRoot.getClass(), newEvents);
      domainEventBus.publish(newEvents);
    }
  }

  @Override
  public <AR extends AggregateRoot, ID extends GenericId> AR load(ID id, Class<AR> aggregateType) {
    try {
      AR aggregateRoot = aggregateType.newInstance();
      aggregateRoot.loadFromHistory(domainEventStore.loadEvents(id));
      return aggregateRoot;
    } catch (IllegalArgumentException iae) {
      String message = format("Aggregate of type [%s] does not exist, ID: %s", aggregateType.getSimpleName(), id);
      throw new IllegalArgumentException(message);
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

} public class GuavaCommandBus implements CommandBus {

  private final EventBus commandBus;

  private GuavaCommandBus(EventBus commandBus) {
    this.commandBus = commandBus;
  }

  public static CommandBus asyncGuavaCommandBus() {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    return new GuavaCommandBus(new AsyncEventBus("commandbus", executorService));
  }

  public static CommandBus syncGuavaCommandBus() {
    return new GuavaCommandBus(new EventBus("commandbus"));
  }

  @Override
  public <T extends CommandHandler> T register(T handler) {
    commandBus.register(handler);
    return handler;
  }

  @Override
  public void dispatch(Command command) {
    commandBus.post(command);
  }

} public class GuavaDomainEventBus implements DomainEventBus {

  private final Logger logger = LoggerFactory.getLogger(getClass());

  private final EventBus eventBus;
  private final EventBus replayBus;

  public GuavaDomainEventBus() {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    eventBus = new AsyncEventBus("eventbus", executorService);
    replayBus = new AsyncEventBus("replaybus", executorService);
  }

  @Override
  public <T extends DomainEventListener> T register(T listener) {
    if (listener.supportsReplay()) {
      replayBus.register(listener);
    }
    eventBus.register(listener);
    return listener;
  }

  @Override
  public void publish(List<DomainEvent> events) {
    for (DomainEvent event : events) {
      eventBus.post(event);
    }
  }

  @Override
  public void republish(List<DomainEvent> events) {
    logger.info("Replaying [{}] events", events.size());
    for (DomainEvent event : events) {
      replayBus.post(event);
    }
  }

} public class ReplayEventsTask extends Task {

  private final DomainEventStore domainEventStore;
  private final DomainEventBus domainEventBus;

  public ReplayEventsTask(DomainEventStore domainEventStore, DomainEventBus domainEventBus) {
    super("republish-events");
    this.domainEventStore = domainEventStore;
    this.domainEventBus = domainEventBus;
  }

  @Override
  public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
    domainEventBus.republish(domainEventStore.getAllEvents());
  }

} public class ProductCatalogClient {

  private final Client client;
  private final String serviceUrl;

  private ProductCatalogClient(Client client, String serviceUrl) {
    this.client = client;
    this.serviceUrl = serviceUrl;
  }

  public static ProductCatalogClient create(Client client, String serviceUrl) {
    return new ProductCatalogClient(client, serviceUrl);
  }

  public ProductDto getProduct(String productId) {
    return client.target(serviceUrl + productId).request().get(ProductDto.class);
  }

} public class InMemOrderProjectionRepository implements OrderProjectionRepository {

  private static final OrderTimestampComparator ORDER_TIMESTAMP_COMPARATOR = new OrderTimestampComparator();
  private final Map<OrderId, OrderProjection> orders = new HashMap<>();

  private void saveOrder(OrderId orderId, OrderProjection orderProjection) {
    orders.put(orderId, orderProjection);
  }

  @Override
  public void save(OrderProjection orderProjection) {
    saveOrder(orderProjection.getOrderId(), orderProjection);
  }

  @Override
  public OrderProjection getById(OrderId orderId) {
    return orders.get(orderId);
  }

  @Override
  public List<OrderProjection> listOrdersByTimestamp() {
    List<OrderProjection> projections = new ArrayList<>(orders.values());
    Collections.sort(projections, Collections.reverseOrder(ORDER_TIMESTAMP_COMPARATOR));
    return projections;
  }

  private static class OrderTimestampComparator implements Comparator<OrderProjection> {
    @Override
    public int compare(OrderProjection o1, OrderProjection o2) {
      return Long.valueOf(o1.getOrderPlacedTimestamp()).compareTo(o2.getOrderPlacedTimestamp());
    }
  }

} public class OrderCommandHandler implements CommandHandler {

  private final Repository repository;

  public OrderCommandHandler(Repository repository) {
    this.repository = repository;
  }

  @Subscribe
  public void handle(PlaceOrderCommand command) {
    Order order = new Order();
    order.place(command.orderId, command.customerInformation, command.orderLines, command.totalAmount);
    repository.save(order);
  }

  @Subscribe
  public void handle(ActivateOrderCommand command) {
    Order order = repository.load(command.orderId, Order.class);
    order.activate();
    repository.save(order);
  }

} public class PublisherContractCommandHandler implements CommandHandler {

  private final Repository repository;

  public PublisherContractCommandHandler(Repository repository) {
    this.repository = repository;
  }

  @Subscribe
  public void handle(RegisterPublisherContractCommand command) {
    PublisherContract contract = new PublisherContract();
    contract.register(command.publisherContractId, command.publisherName, command.feePercentage, command.limit);
    repository.save(contract);
  }

  @Subscribe
  public void handle(RegisterPurchaseCommand command) {
    PublisherContract contract = repository.load(command.publisherContractId, PublisherContract.class);
    contract.registerPurchase(command.productId, command.unitPrice, command.quantity);
    repository.save(contract);
  }

} public class QueryService {

  private final OrderListDenormalizer orderListDenormalizer;
  private final OrdersPerDayAggregator ordersPerDayAggregator;
  private final ProductCatalogClient productCatalogClient;

  public QueryService(OrderListDenormalizer orderListDenormalizer,
                      OrdersPerDayAggregator ordersPerDayAggregator,
                      ProductCatalogClient productCatalogClient) {
    this.orderListDenormalizer = orderListDenormalizer;
    this.ordersPerDayAggregator = ordersPerDayAggregator;
    this.productCatalogClient = productCatalogClient;
  }

  public OrderProjection getOrder(OrderId orderId) {
    return orderListDenormalizer.get(orderId);
  }

  public List<OrderProjection> getOrders() {
    return orderListDenormalizer.getOrders();
  }

  public PublisherContractId findPublisherContract(ProductId productId) {
    ProductDto product = productCatalogClient.getProduct(productId.id);
    return new PublisherContractId(product.publisherContractId);
  }

  public Map<LocalDate, Integer> getOrdersPerDay() {
    return ordersPerDayAggregator.getOrdersPerDay();
  }

} public class OrderListDenormalizer implements DomainEventListener {

  private final Logger logger = LoggerFactory.getLogger(getClass());
  private final OrderProjectionRepository repository;

  public OrderListDenormalizer(OrderProjectionRepository repository) {
    this.repository = repository;
  }

  @Subscribe
  public void handleEvent(OrderPlacedEvent event) {
    logger.info("Received: " + event.toString());
    List<OrderLineProjection> orderLines = new LinkedList<>();
    for (OrderLine orderLine : event.orderLines) {
      OrderLineProjection line = new OrderLineProjection();
      line.productId = orderLine.productId;
      line.quantity = orderLine.quantity;
      line.title = orderLine.title;
      line.unitPrice = orderLine.unitPrice;
      orderLines.add(line);
    }
    OrderProjection orderProjection = new OrderProjection(event.aggregateId, event.timestamp,
        event.customerInformation.customerName, event.orderAmount, orderLines, PLACED);
    repository.save(orderProjection);
  }

  @Subscribe
  public void handleEvent(OrderActivatedEvent event) {
    logger.info("Received: " + event.toString());
    OrderProjection orderProjection = repository.getById(event.aggregateId);
    orderProjection.setStatus(ACTIVATED);
  }

  public List<OrderProjection> getOrders() {
    return repository.listOrdersByTimestamp();
  }

  @Override
  public boolean supportsReplay() {
    return true;
  }

  public OrderProjection get(OrderId orderId) {
    return repository.getById(orderId);
  }

} public interface OrderProjectionRepository {

  void save(OrderProjection orderProjection);

  OrderProjection getById(OrderId orderId);

  List<OrderProjection> listOrdersByTimestamp();

} public class OrdersPerDayAggregator implements DomainEventListener {

  private final TreeMap<LocalDate, Integer> orders = new TreeMap<>();

  @Subscribe
  public void handleEvent(OrderPlacedEvent event) {
    LocalDate localDate = new LocalDate(event.timestamp);
    Integer ordersPerDay = orders.get(localDate);
    orders.put(localDate, nullSafeIncrease(ordersPerDay));
  }

  private Integer nullSafeIncrease(Integer integer) {
    return integer == null ? 1 : integer + 1;
  }

  @Override
  public boolean supportsReplay() {
    return true;
  }

  public Map<LocalDate, Integer> getOrdersPerDay() {
    return orders;
  }

} @Path("order-requests")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
public class OrderResource {

  private final Logger logger = LoggerFactory.getLogger(getClass());
  private final CommandBus commandBus;
  private final CommandFactory commandFactory = new CommandFactory();

  public OrderResource(CommandBus commandBus) {
    this.commandBus = commandBus;
  }

  @POST
  public void placeOrder(@Valid PlaceOrderRequest placeOrderRequest) {
    logger.info("Placing customer order: " + placeOrderRequest);
    PlaceOrderCommand placeOrderCommand = commandFactory.toCommand(placeOrderRequest);
    commandBus.dispatch(placeOrderCommand);
  }

  @POST
  @Path("activations")
  public void activateOrder(@Valid ActivateOrderRequest activationRequest) {
    logger.info("Activating orderId: " + activationRequest.orderId);
    ActivateOrderCommand command = commandFactory.toCommand(activationRequest);
    commandBus.dispatch(command);
  }

} @Path("publisher-contract-requests")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
public class PublisherContractResource {

  private final Logger logger = LoggerFactory.getLogger(getClass());
  private final CommandBus commandBus;
  private final CommandFactory commandFactory = new CommandFactory();

  public PublisherContractResource(CommandBus commandBus) {
    this.commandBus = commandBus;
  }

  @POST
  public void registerContract(@Valid RegisterPublisherContractRequest request) {
    PublisherContractId publisherContractId = new PublisherContractId(request.publisherContractId);
    logger.info("Registering contract: " + publisherContractId);
    RegisterPublisherContractCommand command = commandFactory.toCommand(publisherContractId, request);
    commandBus.dispatch(command);
  }

} @Path("query")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
public class QueryResource {

  private final QueryService queryService;
  private final DomainEventStore eventStore;

  public QueryResource(QueryService queryService, DomainEventStore eventStore) {
    this.queryService = queryService;
    this.eventStore = eventStore;
  }

  @GET
  @Path("events")
  public List<Object[]> getAllEvents() {
    List<DomainEvent> allEvents = eventStore.getAllEvents();
    List<Object[]> eventsToReturn = new LinkedList<>();
    for (DomainEvent event : allEvents) {
      eventsToReturn.add(new Object[]{event.getClass().getSimpleName(), event});
    }
    return eventsToReturn;
  }

  @GET
  @Path("orders")
  public Collection<OrderProjection> getOrders() {
    return queryService.getOrders();
  }

  @GET
  @Path("orders-per-day")
  public Map getOrdersPerDay() {
    return queryService.getOrdersPerDay();
  }

} public class PurchaseRegistrationSaga extends Saga {

  private final Logger logger = LoggerFactory.getLogger(getClass());
  private final QueryService queryService;
  private final CommandBus commandBus;
  private final ExecutorService executorService = Executors.newFixedThreadPool(1);

  public PurchaseRegistrationSaga(QueryService queryService, CommandBus commandBus) {
    this.queryService = queryService;
    this.commandBus = commandBus;
  }

  @Subscribe
  public void handleEvent(OrderActivatedEvent event) {
    logger.info("Received: " + event.toString());
    OrderProjection order = queryService.getOrder(event.aggregateId);
    for (final OrderLineProjection orderLine : order.getOrderLines()) {
      PublisherContractId contractId = queryService.findPublisherContract(orderLine.productId);
      publish(new RegisterPurchaseCommand(contractId, orderLine.productId, orderLine.unitPrice, orderLine.quantity));
    }
  }

  private void publish(final RegisterPurchaseCommand command) {
    executorService.submit(new Runnable() {
      @Override
      public void run() {
        commandBus.dispatch(command);
      }
    });
  }

}
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dropwizard.Application;
import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.citerus.cqrs.bookstore.command.CommandBus;
import se.citerus.cqrs.bookstore.domain.Repository;
import se.citerus.cqrs.bookstore.event.DomainEventBus;
import se.citerus.cqrs.bookstore.event.DomainEventStore;
import se.citerus.cqrs.bookstore.ordercontext.application.infrastructure.DefaultRepository;
import se.citerus.cqrs.bookstore.ordercontext.application.infrastructure.GuavaCommandBus;
import se.citerus.cqrs.bookstore.ordercontext.application.infrastructure.GuavaDomainEventBus;
import se.citerus.cqrs.bookstore.ordercontext.application.task.ReplayEventsTask;
import se.citerus.cqrs.bookstore.ordercontext.client.productcatalog.ProductCatalogClient;
import se.citerus.cqrs.bookstore.ordercontext.infrastructure.InMemOrderProjectionRepository;
import se.citerus.cqrs.bookstore.ordercontext.order.command.OrderCommandHandler;
import se.citerus.cqrs.bookstore.ordercontext.publishercontract.command.PublisherContractCommandHandler;
import se.citerus.cqrs.bookstore.ordercontext.query.QueryService;
import se.citerus.cqrs.bookstore.ordercontext.query.orderlist.OrderListDenormalizer;
import se.citerus.cqrs.bookstore.ordercontext.query.orderlist.OrderProjectionRepository;
import se.citerus.cqrs.bookstore.ordercontext.query.sales.OrdersPerDayAggregator;
import se.citerus.cqrs.bookstore.ordercontext.resource.OrderResource;
import se.citerus.cqrs.bookstore.ordercontext.resource.PublisherContractResource;
import se.citerus.cqrs.bookstore.ordercontext.resource.QueryResource;
import se.citerus.cqrs.bookstore.ordercontext.saga.PurchaseRegistrationSaga;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.ws.rs.client.ClientBuilder;
import java.util.EnumSet;
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
package se.citerus.cqrs.bookstore.ordercontext.application;




public class OrderApplication extends Application<OrderApplicationConfiguration> {
  private final Logger logger = LoggerFactory.getLogger(getClass());

  @Override
  public void initialize(Bootstrap<OrderApplicationConfiguration> bootstrap) {
    bootstrap.addBundle(new AssetsBundle("/assets/", "/", "admin.html"));
  }

  @Override
  public void run(OrderApplicationConfiguration configuration, Environment environment) throws Exception {
    ObjectMapper objectMapper = environment.getObjectMapper();
    objectMapper.enable(INDENT_OUTPUT);
    objectMapper.enable(WRITE_DATES_AS_TIMESTAMPS);

    OrderProjectionRepository orderRepository = new InMemOrderProjectionRepository();
    DomainEventBus domainEventBus = new GuavaDomainEventBus();
    OrderListDenormalizer orderListDenormalizer = domainEventBus.register(new OrderListDenormalizer(orderRepository));
    OrdersPerDayAggregator ordersPerDayAggregator = domainEventBus.register(new OrdersPerDayAggregator());

    ProductCatalogClient catalogClient = ProductCatalogClient.create(ClientBuilder.newClient(), configuration.productCatalogServiceUrl);

    DomainEventStore domainEventStore = (DomainEventStore) configuration.eventStore.newInstance();
    QueryService queryService = new QueryService(orderListDenormalizer, ordersPerDayAggregator, catalogClient);

    logger.info("Using eventStore: " + domainEventStore.getClass().getName());
    Repository aggregateRepository = new DefaultRepository(domainEventBus, domainEventStore);

    CommandBus commandBus = GuavaCommandBus.asyncGuavaCommandBus();
    commandBus.register(new OrderCommandHandler(aggregateRepository));
    commandBus.register(new PublisherContractCommandHandler(aggregateRepository));

    // Create and register Sagas
    PurchaseRegistrationSaga purchaseRegistrationSaga = new PurchaseRegistrationSaga(queryService, commandBus);