Sunday, August 17, 2014

Domain-Driven Design: Cargo Shipping Example (Part 2)

One of my colleagues asked an interesting question about my previous post that I thought would be worth posting here. He asked about the persistence of value objects. He pointed out that the Cargo had a Delivery property that was replaced (not modified). So, what happens to its persistence? Does it result into a delete and an insert? Let's see.

Let's see how a Delivery object is used in a Cargo object. In the highlighted lines, we can see that the property is indeed being assigned (not modified or updated) with a new delivery object.

public class Cargo implements Entity<Cargo> {

  private TrackingId trackingId;
  private Location origin;
  private RouteSpecification routeSpecification;
  private Itinerary itinerary;
  private Delivery delivery;

  public Cargo(final TrackingId trackingId, final RouteSpecification routeSpecification) {
    Validate.notNull(trackingId, "Tracking ID is required");
    Validate.notNull(routeSpecification, "Route specification is required");

    this.trackingId = trackingId;
    // Cargo origin never changes, even if the route specification changes.
    // However, at creation, cargo orgin can be derived from the initial route specification.
    this.origin = routeSpecification.origin();
    this.routeSpecification = routeSpecification;

    this.delivery = Delivery.derivedFrom(
      this.routeSpecification, this.itinerary, HandlingHistory.EMPTY
    );
  }

  public TrackingId trackingId() { return trackingId; }

  public Location origin() { return origin; }

  public Delivery delivery() { return delivery; }

  public Itinerary itinerary() {
    return DomainObjectUtils.nullSafe(
        this.itinerary, Itinerary.EMPTY_ITINERARY);
  }

  public RouteSpecification routeSpecification() { return routeSpecification; }

  public void specifyNewRoute(final RouteSpecification routeSpecification) {
    Validate.notNull(routeSpecification, "Route specification is required");

    this.routeSpecification = routeSpecification;
    // Handling consistency within the Cargo aggregate synchronously
    this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
  }

  public void assignToRoute(final Itinerary itinerary) {
    Validate.notNull(itinerary, "Itinerary is required for assignment");

    this.itinerary = itinerary;
    // Handling consistency within the Cargo aggregate synchronously
    this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
  }

  public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
    // TODO filter events on cargo (must be same as this cargo)

    // Delivery is a value object, so we can simply discard the old one
    // and replace it with a new
    this.delivery = Delivery.derivedFrom(routeSpecification(), itinerary(), handlingHistory);
  }
  . . .
}

Here is the Delivery value object (summarized). It doesn't have mutator methods. The class is immutable. Note that updateOnRouting() and derivedFrom() methods return new Delivery objects.

public class Delivery implements ValueObject<Delivery> {

  private TransportStatus transportStatus;
  private Location lastKnownLocation;
  private Voyage currentVoyage;
  private boolean misdirected;
  private Date eta;
  private HandlingActivity nextExpectedActivity;
  private boolean isUnloadedAtDestination;
  private RoutingStatus routingStatus;
  private Date calculatedAt;
  private HandlingEvent lastEvent;

  private static final Date ETA_UNKOWN = null;
  private static final HandlingActivity NO_ACTIVITY = null;

  /**
   * Creates a new delivery snapshot to reflect changes in routing, i.e.
   * when the route specification or the itinerary has changed
   * but no additional handling of the cargo has been performed.
   *
   * @param routeSpecification route specification
   * @param itinerary itinerary
   * @return An up to date delivery
   */
  Delivery updateOnRouting(RouteSpecification routeSpecification, Itinerary itinerary) {
    Validate.notNull(routeSpecification, "Route specification is required");

    return new Delivery(this.lastEvent, itinerary, routeSpecification);
  }

  /**
   * Creates a new delivery snapshot based on the complete handling history of a cargo,
   * as well as its route specification and itinerary.
   *
   * @param routeSpecification route specification
   * @param itinerary itinerary
   * @param handlingHistory delivery history
   * @return An up to date delivery.
   */
  static Delivery derivedFrom(
       RouteSpecification routeSpecification,
       Itinerary itinerary,
       HandlingHistory handlingHistory) {
    Validate.notNull(routeSpecification, "Route specification is required");
    Validate.notNull(handlingHistory, "Delivery history is required");

    final HandlingEvent lastEvent = handlingHistory.mostRecentlyCompletedEvent();

    return new Delivery(lastEvent, itinerary, routeSpecification);
  }

  /**
   * Internal constructor.
   */
  private Delivery(
        HandlingEvent lastEvent, Itinerary itinerary,
        RouteSpecification routeSpecification) {
    this.calculatedAt = new Date();
    this.lastEvent = lastEvent;

    this.misdirected = calculateMisdirectionStatus(itinerary);
    this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
    this.transportStatus = calculateTransportStatus();
    this.lastKnownLocation = calculateLastKnownLocation();
    this.currentVoyage = calculateCurrentVoyage();
    this.eta = calculateEta(itinerary);
    this.nextExpectedActivity = calculateNextExpectedActivity(routeSpecification, itinerary);
    this.isUnloadedAtDestination = calculateUnloadedAtDestination(routeSpecification);
  }

  public TransportStatus transportStatus() {...}

  public Location lastKnownLocation() {...}

  public Voyage currentVoyage() {...}

  public boolean isMisdirected() {...}

  public Date estimatedTimeOfArrival() {...}

  public HandlingActivity nextExpectedActivity() {...}

  public boolean isUnloadedAtDestination() {...}

  public RoutingStatus routingStatus() {...}

  public Date calculatedAt() {...}
  . . .
}

The above entities don't use JPA annotations. So, I dug into the example's ORM which happens to be Hibernate (Cargo.hbm.xml). I saw the following:

<hibernate-mapping default-access="field">
  <class name="se.citerus.dddsample.domain.model.cargo.Cargo" table="Cargo">
    <id name="id" column="id">...</id>

    <many-to-one name="origin" column="origin_id" not-null="false" cascade="none" update="false" foreign-key="origin_fk"/>

    <component name="trackingId" unique="true" update="false">...</component>

    <component name="delivery" lazy="true">
      <property name="misdirected" column="is_misdirected" not-null="true"/>
      <property name="eta" column="eta" not-null="false"/>
      <property name="calculatedAt" column="calculated_at" not-null="true"/>
      <property name="isUnloadedAtDestination" column="unloaded_at_dest" not-null="true"/>

      <property name="routingStatus" column="routing_status" not-null="true">...</property>

      <component name="nextExpectedActivity" update="true">
        <many-to-one name="location" column="next_expected_location_id" foreign-key="next_expected_location_fk" cascade="none"/>
        <property name="type" column="next_expected_handling_event_type">...</property>
        <many-to-one name="voyage" column="next_expected_voyage_id" foreign-key="next_expected_voyage_fk" cascade="none"/>
      </component>

      <property name="transportStatus" column="transport_status" not-null="true">...</property>
      <many-to-one name="currentVoyage" column="current_voyage_id" not-null="false" cascade="none" foreign-key="current_voyage_fk"/>
      <many-to-one name="lastKnownLocation" column="last_known_location_id" not-null="false" cascade="none" foreign-key="last_known_location_fk"/>
      <many-to-one name="lastEvent" column="last_event_id" not-null="false" cascade="none" foreign-key="last_event_fk"/>
    </component>
    ...
  </class>
</hibernate-mapping>

The delivery value object is declared as a <component>. A component is a contained object that is persisted as a value type and not an entity reference. That means, it does not have its own table. Instead, its properties are mapped as columns in the surrounding <class>'s table. So here, the delivery object's properties are actually columns in the Cargo table.

So, does it result into a delete and an insert? The answer is neither. It results into an update!

Note that not all value objects will be persisted this way. The Leg value objects are deleted whenever a new Itinerary object is assigned to a Cargo. This results into Leg value objects being inserted (after the old ones are deleted). So, please check your ORM and persistence mechanisms to be sure, and see if there are any performance problems.

But why all the hassle just to use value objects? Good question. Here's what the cargo shipping example has to say:

When possible, we tend to favor Value Objects over Entities or Domain Events, because they require less attention during implementation. Value Objects can be created and thrown away at will, and since they are immutable we can pass them around as we wish.

So, I guess in the cargo shipping example, the likelihood of an itinerary being replaced is quite minimal. Thus, they didn't have performance problems even when the child leg objects were being deleted (and new ones inserted).

Monday, August 11, 2014

Purpose of SOA

After having a few more discussions about SOA (after my previous post on the topic), I happen to watch this video (again) where Anne Thomas Manes (now with Gartner via the acquisition of Burton Group) gave a talk at QCon London entitled The Business Value of SOA. Yeah, it's an old video. I'm glad to have watched it again. It helps me go back to the basics, especially with all the on-going confusion with SOA, ESB, SOAP, XML, REST, and the more recent hype around microservices (µservices). In this post, I'll be quoting Anne and writing my takeaways from her 2007 talk. Near the end, I refer to a 2009 blog post from Anne, and a more recent 2012 interview.

Why SOA?

Before I go about dealing with SOA and what it's all about, I'd like to start with why. Why SOA? In Anne's talk, she pointed out that about 80% of IT budget goes into maintenance and operations. That was 2007. In a 2013 Computerworld article entitled, How to Balance Maintenance and IT Innovation, it mentioned that a significant portion of IT budget goes to maintenance and operations:

In a recent Forrester Research survey of IT leaders at more than 3,700 companies, respondents estimated that they spend an average 72% of the money in their budgets on such keep-the-lights-on functions as replacing or expanding capacity and supporting ongoing operations and maintenance, while only 28% of the money goes toward new projects.

It seems that very little has changed. In Anne's 2007 talk, she pointed out that the problem is not the new applications, but the high cost of maintaining and operating existing applications. Why does an enterprise have so many applications? An enterprise would usually have about 20 or so core capabilities. But why would it have over 400 applications?

Anne points out that redundancy is the source of the problem. She says that there are too many applications, databases, too much money spent on boat anchors, and very little money left for innovation. The purpose of SOA is to reduce redundancy.

So, why SOA? SOA tries to reduce the cost of maintenance and operations by refactoring redundancy. Newer applications should cost less to build too, since there would be a set of services to re-use. With new applications, or newly applied changes to existing applications, organizations can cope with changes in business and become more agile.

SOA Concepts

SOA is an architecture for designing systems. … What is architecture? Architecture is a style of design. Anne said. She stressed that it is a design for systems and not applications.

She continues by explaining that in SOA, a service is the core unit of design. So what's a service?

A service is something that implements a discrete piece of functionality. It exposes its functionality through a well-defined interface.… so that applications can consume it. Anne said.

She points out that the service should be consumed by many applications. And in fact, the service should be consumed by many applications. And in fact, if you were building a service that is designed to be consumed by a single application, you're probably wasting your time. Anne said. And I actually want to stress this word consume. Applications consume services. SOA is not about application to application integration. That's how a lot of people use it today. They use SOA technologies to integrate applications. But actually your goal when you're doing SOA is to say what is the capability that is required by multiple applications. Rather than reimplementing that capability many times over in each application that needs it, you refactor the functionality into a service. And then applications consume it.

If you're figuring out which functionality/capability is required by multiple applications, just look around. Look at the existing applications. Look at your application portfolio. Identify which data and/or functionality is being used in multiple applications. Refactor them out as services!

If a functionality is required by many applications, it should be implemented as a service. And it should not be reimplemented in multiple applications.

If the phrase shared, reusable services is too vague for you or your team, then try using refactor redundancy. If the phrases suite of small services running in its own process and communicating with lightweight mechanisms, often an HTTP resource API or services built around business capabilities and independently deployable, are a bit confusing, or a bit intimidating, try using reducing redundancy.

I find the term microservices to be further misleading. I prefer to focus on the goal of reducing redundancy, than using the latest term that is in vogue.

What SOA is Not?

At about 11:20 into the video, Anne talked about what SOA is not, and touched on why business units are not thrilled about SOA. She says it is not an application-centric approach to building systems. And this is actually one of the most challenging things about SOA, because all your projects are funded based on applications. [If] you think about it, whenever there's a new project, the project that is funded by a business unit, the business unit wants you to build an application to solve a particular problem. The business unit has no interest whatsoever in having you spend extra time to go off and create a piece of capability in this application as a service, which is then gonna get consumed by some other set of business applications by some other business units.

And so there's a whole lot of business reasons, incentive reasons, why business units aren't too thrilled with the idea of you spending their money that will wind up benefiting other people.

Here, Anne touched a bit on culture and how business units behave. I've witnessed this myself. It's difficult if the incentives of a business unit is just to solve their own problems, and not cooperate with other business units to save organization-wide costs, reduce time to market, and increase competitiveness. So, SOA isn't just an IT initiative. It needs to be a business initiative.

Then Anne continues, But, that's the way people build systems today. And what they're building are monolithic applications. And when you build monolithic applications, you tend to reproduce the same functionality and comparable data in many different systems. And this duplication, this redundancy is actually the source of most of the trouble in today's IT systems.

Now, here are some more of my takeaways from Anne's talk:

  • SOA is about design, not about technology.
  • SOA is something you do, and not something you build or buy.
  • SOA is much more about culture than about technology.
  • You can't buy [SOA] governance. It's something that you do. (from OTN Archbeat Podcast: SOA Governance)

All of the above was from a talk Anne gave in 2007. Forward a little bit into early , Anne blogged that SOA is dead. Three years after that, in 2012, Anne Thomas Manes says, people are starting to actually get the architecture. Well, I'm glad to see that we're all starting to get it.

Closing Thoughts

Anne Thomas Manes did a great job peeling away all the technology, and allowed SOA's true value to emerge. For me, she was able to answer the why of SOA, which could be easily lost with all the technology and products that vendors are trying to sell.

After applying SOA to your business, you ought to be asking yourself how much redundancy you've refactored out, and how much time and money these shared services have provided the organization. For measuring the business value of SOA, please watch another talk by Anne Thomas Manes.

This post sort of takes me back in time when SOA was confusing (and probably still is), and helps me clear some things up. I hope it helps you clear things up too.