纪事世界中的微服务–第1部分

在高层次上,不同的微服务策略有很多共同点。 他们认同同样的理想。 当涉及到如何实际实施的细节时,它们可能会有所不同。

纪事世界中的微服务围绕以下方面进行设计:

  • 简单-简单,快速,灵活且易于维护。
  • 透明度-您无法控制自己不了解的内容。
  • 可复制性-这必须在您的设计中以确保质量解决方案。

简单是什么意思?

微服务设计的关键部分是如何在服务/组件之间传递消息。 最简单的消息可以称为异步方法调用

异步方法调用是这样的:

  • 什么也不会返回
  • 不会改变其论点;
  • 不会引发任何异常(尽管基础传输可以)。

使用这种方法的原因是,最简单的运输就是根本没有运输。 一个组件调用另一个组件。 这不仅快速(而且内联可能根本不需要任何时间),而且设置,调试和配置文件都很简单。 对于大多数单元测试,您不需要真正的传输,因此使测试变得比需要的复杂没有任何优势。

让我们看一个例子。

假设我们有一个服务/组件正在对市场数据进行增量更新。 在最简单的情况下,这可能是仅包含一侧的买入或卖出的市场更新。 该组件可以将其转变为结合了买卖价格和数量的完整市场更新。

在此示例中,我们只有一种消息类型开始,但是可以添加更多消息类型。 我建议您为每个消息创建一个不同的消息名称/方法,而不是重载方法。

我们的入站数据结构

public class SidedPrice extends AbstractMarshallable {
    final String symbol;
    final long timestamp;
    final Side side;
    final double price, quantity;

    public SidedPrice(String symbol, long timestamp, Side side, double price, double quantity) {
        this.symbol = symbol;
        this.timestamp = timestamp;
        this.side = side;
        this.price = price;
        this.quantity = quantity;
    }
}

我们的出站数据结构

public class TopOfBookPrice extends AbstractMarshallable {
    final String symbol;
    final long timestamp;
    final double buyPrice, buyQuantity;
    final double sellPrice, sellQuantity;

    public TopOfBookPrice(String symbol, long timestamp, double buyPrice, double buyQuantity, double sellPrice, double sellQuantity) {
        this.symbol = symbol;
        this.timestamp = timestamp;
        this.buyPrice = buyPrice;
        this.buyQuantity = buyQuantity;
        this.sellPrice = sellPrice;
        this.sellQuantity = sellQuantity;
    }

    // more methods (1)
}
小费
有关完整的代码TopOfBookPrice.java

附带价格的组件可以有一个接口;

第一个组件的入站接口

public interface SidedMarketDataListener {
    void onSidedPrice(SidedPrice sidedPrice);
}

它的输出也只有一种方法;

第一个组件的出站接口

public interface MarketDataListener {
    void onTopOfBookPrice(TopOfBookPrice price);
}

我们的微服务是什么样的?

在较高的层次上,组合器非常简单。

public class SidedMarketDataCombiner implements SidedMarketDataListener {
    final MarketDataListener mdListener;
    final Map<String, TopOfBookPrice> priceMap = new TreeMap<>();

    public SidedMarketDataCombiner(MarketDataListener mdListener) {
        this.mdListener = mdListener;
    }

    public void onSidedPrice(SidedPrice sidedPrice) {
        TopOfBookPrice price = priceMap.computeIfAbsent(sidedPrice.symbol, TopOfBookPrice::new);
        if (price.combine(sidedPrice))
            mdListener.onTopOfBookPrice(price);
    }
}

它实现了我们的输入接口,并将输出接口作为侦听器。

AbstractMarshallable提供了什么?

AbstractMarshallable类是一个便利类,它实现toString()equals(Object)hashCode() 。 它还支持Marshallable的writeMarshallable(WireOut)readMarshallable(WireIn)

默认实现使用所有非静态非瞬态字段来打印,比较或构建hashCode。

小费
生成的toString()始终可以使用Marshallable.fromString(CharSequence)进行反序列化

让我们看几个例子。

SidedPrice sp = new SidedPrice("Symbol", 123456789000L, Side.Buy, 1.2345, 1_000_000);
assertEquals("!SidedPrice {\n" +
        "  symbol: Symbol,\n" +
        "  timestamp: 123456789000,\n" +
        "  side: Buy,\n" +
        "  price: 1.2345,\n" +
        "  quantity: 1000000.0\n" +
        "}\n", sp.toString());

// from string
SidedPrice sp2 = Marshallable.fromString(sp.toString());
assertEquals(sp2, sp);
assertEquals(sp2.hashCode(), sp.hashCode());

如您所见, toString()以简洁的YAML格式编写,对人类和代码均可读。

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000);
assertEquals("!TopOfBookPrice {\n" +
        "  symbol: Symbol,\n" +
        "  timestamp: 123456789000,\n" +
        "  buyPrice: 1.2345,\n" +
        "  buyQuantity: 1000000.0,\n" +
        "  sellPrice: 1.235,\n" +
        "  sellQuantity: 2000000.0\n" +
        "}\n", tobp.toString());

// from string
TopOfBookPrice topb2 = Marshallable.fromString(tobp.toString());
assertEquals(topb2, tobp);
assertEquals(topb2.hashCode(), tobp.hashCode());
}

使用这种格式的优点之一是,即使在复杂的对象中,它也使查找失败测试的原因变得更加容易。

即使进行了琐碎的测试,问题也不明显

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000);
TopOfBookPrice tobp2 = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.236, 2_000_000);

assertEquals(tobp, tobp2);

但是,当您在IDE中运行此测试时,会出现一个比较窗口。

图1. IDE中的比较窗口

如果您有一个较大的嵌套/复杂对象,而assertEquals失败,那么它实际上可以为您节省大量时间来查找差异。

模拟我们的组件

我们可以使用EasyMock之类的工具来模拟界面。 我发现EasyMock在处理事件驱动的接口时更简单。 它不如PowerMock或Mockito强大,但是如果您使事情简单,则可能不需要这些功能。

// what we expect to happen
SidedPrice sp = new SidedPrice("Symbol", 123456789000L, Side.Buy, 1.2345, 1_000_000);
SidedMarketDataListener listener = createMock(SidedMarketDataListener.class);
listener.onSidedPrice(sp);
replay(listener);

// what happens
listener.onSidedPrice(sp);

// verify we got everything we expected.
verify(listener);

我们还可以用相同的方法模拟组件的预期输出。

测试我们的组件

通过模拟输出接口并为我们的组件调用输入接口,我们可以检查其行为是否符合预期。

MarketDataListener listener = createMock(MarketDataListener.class);
listener.onTopOfBookPrice(new TopOfBookPrice("EURUSD", 123456789000L, 1.1167, 1_000_000, Double.NaN, 0)); (1)
listener.onTopOfBookPrice(new TopOfBookPrice("EURUSD", 123456789100L, 1.1167, 1_000_000, 1.1172, 2_000_000)); (2)
replay(listener);

SidedMarketDataListener combiner = new SidedMarketDataCombiner(listener);
combiner.onSidedPrice(new SidedPrice("EURUSD", 123456789000L, Side.Buy, 1.1167, 1e6)); (1)
combiner.onSidedPrice(new SidedPrice("EURUSD", 123456789100L, Side.Sell, 1.1172, 2e6)); (2)

verify(listener);
Setting the buy price
Setting the sell price

测试一系列组件

让我们添加一个OrderManager作为下游组件。 该订单经理将同时接收市场数据更新和订单提示,然后生成订单。

// what we expect to happen
OrderListener listener = createMock(OrderListener.class);
listener.onOrder(new Order("EURUSD", Side.Buy, 1.1167, 1_000_000));
replay(listener);

// build our scenario
OrderManager orderManager = new OrderManager(listener); (2)
SidedMarketDataCombiner combiner = new SidedMarketDataCombiner(orderManager); (1)

// events in
orderManager.onOrderIdea(new OrderIdea("EURUSD", Side.Buy, 1.1180, 2e6)); // not expected to trigger

combiner.onSidedPrice(new SidedPrice("EURUSD", 123456789000L, Side.Sell, 1.1172, 2e6));
combiner.onSidedPrice(new SidedPrice("EURUSD", 123456789100L, Side.Buy, 1.1160, 2e6));

combiner.onSidedPrice(new SidedPrice("EURUSD", 123456789100L, Side.Buy, 1.1167, 2e6));

orderManager.onOrderIdea(new OrderIdea("EURUSD", Side.Buy, 1.1165, 1e6)); // expected to trigger

verify(listener);
The first component combines sided prices
The second component listens to order ideas and top of book market data

调试我们的组件

您可以看到一个组件只是调用了另一个。 在调试此单线程代码时,来自第一个组件的每个事件都是对第二个组件的调用。 完成后,将返回第一个测试。

当任何单个事件触发错误时,您都可以在堆栈跟踪中看到导致该问题的事件。 但是,如果您期待不会发生的事件,那么除非您的测试很简单(或者您使用verify()reset()replay()进行一系列简单测试verify() ,否则这会很麻烦。

小费
在IDE中几乎不需要任何时间就可以启动测试和调试。 您可以在一秒钟之内运行数百个这样的测试。

示例来源

https://github.com/Vanilla-Java/Microservices/tree/master/src/main/java/net/openhft/samples/microservices

我们如何将它们创建为服务?

我们已经展示了测试和调试组件是多么容易。 在第二部分中,我们如何将它们转变为服务

翻译自: https://www.javacodegeeks.com/2017/02/microservices-chronicle-world-part-1.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值