Micro Services

light and dark side

Created by Marcin Kuthan

The Talk

  • Micro Services Architecture
  • Contoso Conference Management System
  • Challenges
  • How to start?

Monolithic Architecture

Monolithic Architecture

Micro Services Architecture

The eBay Architecture

MicroService Architecture, personal journey of discovery

Micro Services, Java the Unix way

Micro Services Architecture

SOA done right

Micro Services Architecture

Small enough to fit in your head

Micro Services Architecture

Does one thing well

Micro Services Architecture

Small enough that you can throw it away

Micro Services Architecture

Uses right tool for the job

Micro Services Architecture

Loosely coupled

Micro Services Architecture

Independent

Micro Services Architecture

Favor service choreography over orchestration

Micro Services Architecture

Smart endpoints, dumb pipes

Micro Services Architecture

Design for failure

Micro Services Architecture

Empower teams and ownership

Micro Services Architecture

You build it, you run it

Micro Services Architecture

Two Pizza Team

Micro Services Architecture

Any comments?

Contoso Conference Management System

CQRS Journey

Contoso Conference Management System

Domain

Domain

Selling seats for a conference

Domain

Creating a conference

Contoso Conference Management System

Decomposing the domain

Decomposing the domain

Orders and Registrations

Decomposing the domain

Conference Management

Decomposing the domain

Payments

Decomposing the domain

Summary

Conference Management

Conference Management

Model (1)


@Entity
class Conference {
  String id
  String name
  String location
  Boolean published
  Set<SeatType> seatTypes
  ... // speakers, schedule, etc.
}
        

Conference Management

Model (2)


@Entity
class SeatType {
  Conference conference
  String id
  String name
  String description
  int quantity
  BigDecimal priceAmount
  String priceCurrency
  ... // rule based pricing
}
        

Conference Management

Repositories


@RepositoryRestResource
interface ConferenceRepository extends JpaRepository<Conference, String> {
}

@RepositoryRestResource
interface SeatTypeRepository extends JpaRepository<SeatType, String> {
}
        

Conference Management

Integration (1)



@RepositoryEventHandler(Conference.class)
class ConferenceEventHandler {
  @HandleAfterCreate
  void publishConferenceCreatedEvent(Conference conference) {
      ConferenceCreated event = new ConferenceCreated()

      event.setConferenceId(conference.getId())
      event.setName(conference.getName())
      ...

      eventBus.publish(event)
  }

  @Autowired
  EventBus eventBus
}
        

Conference Management

Integration (2)


@RepositoryEventHandler(SeatType.class)
class SeatTypeEventHandler {
  @HandleAfterCreate
  void publishSeatTypeCreatedEvent(SeatType seatType) {
    SeatTypeCreated event = new SeatTypeCreated()

    event.setSeatTypeId(seatType.getId())
    event.setConferenceId(seatType.getConference().getId())
    ...

    eventBus.publish(event)
  }

  @Autowired
  EventBus eventBus
}
        

Conference Management

Wrap Up


@Configuration
@EnableAutoConfiguration
@ComponentScan
class ManagementApplication {
  public static void main(String[] args) {
    SpringApplication.run(ManagementApplication.class, args);
  }
}
        

Conference Management

Demo

Orders and Registrations

Mockups

Workflow

Orders and Registrations

Model (1)


@Entity
class Order {
  String orderId
  String conferenceId
  List<OrderItem> items
  ... // registrant, status, payment
}

@Entity
class OrderItem {
  String seatTypeId
  int quantity
  ... // attendees, status
}
      

Orders and Registrations

Model (2)


@Entity
class SeatsAvailability {
  String conferenceId
  Set<SeatAvailability> availableSeats
  makeSeatsReservation(...)
  cancelSeatsReservation(...)
  ... // addSeats(), removeSeats()
}
@Entity
class SeatAvailability {
  String seatTypeId
  int quantity
}
        

Orders and Registrations

Integration (1)


@EventHandler
void on(ConferenceCreated event) {
  seatsAvailability = new SeatsAvailability(event.getConferenceId())
  repository.save(seatsAvailability)
}

@EventHandler
void on(ConferenceUpdated event) {
  seatsAvailability = repository.load(event.getConferenceId())

  if (event.isPublished()) {
    ...
  } else {
    ...
  }
}
        

Orders and Registrations

Integration (2)


@EventHandler
void on(SeatTypeUpdated event) {
  seatsAvailability = repository.load(event.getConferenceId())

  int difference = // How to calculate diff?
  if (difference > 0) {
    seatsAvailability.addSeats(event.getSeatTypeId(), difference)
  } else if (difference < 0) {
    seatsAvailability.removeSeats(event.getSeatTypeId(), abs(difference))
  }
}
        

Orders and Registrations

Business Processes (1)


@SagaEventHandler
void on(OrderPlaced event) {
  makeSeatsReservation = new MakeSeatsReservation(
            event.getOrderId(), event.getConferenceId(), event.getSeats())
  commandGateway.send(makeSeatsReservation)

  orderExpired = new RegistrationExpired(event.getOrderId())
  eventScheduler.schedule(event.getTimeout(), orderExpired)
}

@Autowired
CommandGateway commandGateway

@Autowired
EventScheduler eventScheduler
        

Orders and Registrations

Business Processes (2)


@SagaEventHandler(associationProperty = "orderId")
public void on(SeatsReservationAccepted event) {
  seatsReservationAccepted = true

  if (paymentReceived) {
    confirmOrder(event.getOrderId())
  }
}
// saga state
boolean seatsReservationAccepted = false
boolean paymentReceived = false
        

Orders and Registrations

Business Processes (3)


@SagaEventHandler(associationProperty = "orderId")
public void on(PaymentReceived event) {
  paymentReceived = true

  if (seatsReservationAccepted) {
    confirmOrder(event.getOrderId())
  }
}
// saga state
boolean seatsReservationAccepted = false
boolean paymentReceived = false
        

Orders and Registrations

Business Processes (4)


@SagaEventHandler(associationProperty = "orderId")
public void on(RegistrationExpired event) {
  if (seatsReservationAccepted) {
    cancelSeatsReservation = new CancelSeatsReservation(
            event.getOrderId(), event.getConferenceId(), event.getSeats())
    commandGateway.send(cancelSeatsReservation)
  }

  if (paymentReceived) {
    // TODO: How to compensate payment?
  }

  rejectOrder(event.getOrderId());
}
        

Orders and Registrations

Wrap Up


@Configuration
@EnableAutoConfiguration
@ComponentScan
class RegistrationsApplication {
  public static void main(String[] args) {
    SpringApplication.run(RegistrationsApplication.class, args);
  }
}
        

Orders and Registrations

Demo

Payments

Payments

Out of scope

Architecture Challenges

Architecture Challenges

Essential Complexity

  • How to decompose the domain?
  • How to accept eventual consistency?

Architecture Challenges

Accidental Complexity

  • How to test the system?
  • How to setup infrastructure?
  • How to avoid 2PC transactions?
  • How to implement Composite UI?
  • How to monitor asynchronous communication?
  • How to version, release and deploy moving parts?

Distributed Architecture

Martin Fowler’s First Law of Distribution

http://martinfowler.com/bliki/FirstLaw.html

Don’t

Fallacies of Distributed Computing

http://www.rgoarchitects.com/Files/fallacies.pdf

  • Network is reliable
  • Latency is zero
  • Bandwidth is infinite
  • Transport cost is zero
  • Network is secure
  • Topology doesn’t change
  • There is one administrator
  • Network is homogeneous

How to start?

How to start?

Single JVM

Don’t distribute, deploy on single JVM.

How to start?

Decompose

Don’t develop monoliths, decompose application.

How to start?

Published Language

Don’t expose module internals, for every package define published language.

How to start?

Business Identifiers

Don’t use entity references, use only business identifiers as references.


class SeatsAvailability {
  ConferenceId conferenceId
  ...
}
        

How to start?

Event Bus (1)

Don’t call us, we’ll call you.


@Aspect
class EventsPublishingAspect {

  @AfterReturning(pointcut = "execution(public * *.Repository+.save(..))")
  public void publishPendingEvents(AggregateRoot aggregateRoot) {
    for (Event event : aggregateRoot.getPendingEvents()) {
      eventPublisher.publish(event)
    }
  }

  @Autowired
  EventPublisher eventPublisher
}
        

How to start?

Event Bus (2)

Don’t rely on “Unit of Work” but still handle events in the caller thread.


@Component
public class EventBusBeanPostProcessor implements BeanPostProcessor {

  Object postProcessAfterInitialization(Object bean, String beanName) {
    if (beanFactory.isSingleton(beanName)) {
      eventBus.register(bean);
    }
    return bean;
  }

  @Autowired
  com.google.common.eventbus.EventBus eventBus;
}
        

How to start?

Event Bus (3)

Process events asynchronously, eventually.


@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
  ThreadFactory threadFactory = new ThreadFactoryBuilder()
    .setNameFormat("MyTaskExecutor-%d")
    .build()

  return Executors.newScheduledThreadPool(
    environment.getRequiredProperty("scheduling.taskExecutorCorePoolSize"),
    threadFactory)
}

@Autowired
Environment environment
        

How to start?

Saga

Manage long running business processes explicitly using Saga pattern.

How to start?

Event Sourcing

Manage domain complexity by separating write and read models.

How to start?

Keep trying

Expect challenges.

The End

Visit my blog: mkuthan.github.io