阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

设计订单系统

74次阅读
没有评论

共计 3627 个字符,预计需要花费 10 分钟才能阅读完成。

上一节我们实现了一个资产系统,本节我们来设计并实现一个订单系统。

订单系统的目的是为了管理所有的活动订单,并给每个新订单一个递增的序列号。由于在创建订单时需要冻结用户资产,因此,我们定义的 OrderService 会引用AssetService

public class OrderService {// 引用 AssetService:
    final AssetService assetService;

    public OrderService(@Autowired AssetService assetService) {this.assetService = assetService;
    }
}

一个订单由订单 ID 唯一标识,此外,订单包含以下重要字段:

  • userId:订单关联的用户 ID;
  • sequenceId:定序 ID,相同价格的订单根据定序 ID 进行排序;
  • direction:订单方向:买或卖;
  • price:订单价格;
  • quantity:订单数量;
  • unfilledQuantity:尚未成交的数量;
  • status:订单状态,包括等待成交、部分成交、完全成交、部分取消、完全取消。

一个订单被成功创建后,它后续由撮合引擎处理时,只有 unfilledQuantitystatus会发生变化,其他属性均为只读,不会改变。

当订单状态变为完全成交、部分取消、完全取消时,订单就已经处理完成。处理完成的订单从订单系统中删除,并写入数据库永久变为历史订单。用户查询活动订单时,需要读取订单系统,用户查询历史订单时,只需从数据库查询,就与订单系统无关了。

我们定义 OrderEntity 如下:

public class OrderEntity {// 订单 ID / 定序 ID / 用户 ID:
    public Long id;
    public long sequenceId;
    public Long userId;

    // 价格 / 方向 / 状态:
    public BigDecimal price;
    public Direction direction;
    public OrderStatus status;

    // 订单数量 / 未成交数量:
    public BigDecimal quantity;
    public BigDecimal unfilledQuantity;

    // 创建和更新时间:
    public long createdAt;
    public long updatedAt;
}

处于简化设计的缘故,该对象既作为订单系统的订单对象,也作为数据库映射实体。

根据业务需要,订单系统需要支持:

  • 根据订单 ID 查询到订单;
  • 根据用户 ID 查询到该用户的所有活动订单。

因此,OrderService需要用两个 Map 存储活动订单:

public class OrderService {// 跟踪所有活动订单: Order ID => OrderEntity
    final ConcurrentMap<Long, OrderEntity> activeOrders = new ConcurrentHashMap<>();

    // 跟踪用户活动订单: User ID => Map(Order ID => OrderEntity)
    final ConcurrentMap<Long, ConcurrentMap<Long, OrderEntity>> userOrders = new ConcurrentHashMap<>();

添加一个新的 Order 时,需要同时更新 activeOrdersuserOrders。同理,删除一个 Order 时,需要同时从 activeOrdersuserOrders中删除。

我们先编写创建订单的方法:

/**
 * 创建订单,失败返回 null:
 */
public OrderEntity createOrder(long sequenceId, long ts, Long orderId, Long userId, Direction direction, BigDecimal price, BigDecimal quantity) {switch (direction) {case BUY -> {// 买入,需冻结 USD:
        if (!assetService.tryFreeze(userId, AssetEnum.USD, price.multiply(quantity))) {return null;
        }
    }
    case SELL -> {// 卖出,需冻结 BTC:
        if (!assetService.tryFreeze(userId, AssetEnum.BTC, quantity)) {return null;
        }
    }
    default -> throw new IllegalArgumentException("Invalid direction.");
    }
    // 实例化 Order:
    OrderEntity order = new OrderEntity();
    order.id = orderId;
    order.sequenceId = sequenceId;
    order.userId = userId;
    order.direction = direction;
    order.price = price;
    order.quantity = quantity;
    order.unfilledQuantity = quantity;
    order.createdAt = order.updatedAt = ts;
    // 添加到 ActiveOrders:
    this.activeOrders.put(order.id, order);
    // 添加到 UserOrders:
    ConcurrentMap<Long, OrderEntity> uOrders = this.userOrders.get(userId);
    if (uOrders == null) {uOrders = new ConcurrentHashMap<>();
        this.userOrders.put(userId, uOrders);
    }
    uOrders.put(order.id, order);
    return order;
}

后续在清算过程中,如果发现一个 Order 已经完成或取消后,需要调用删除方法将活动订单从 OrderService 中删除:

public void removeOrder(Long orderId) {// 从 ActiveOrders 中删除:
    OrderEntity removed = this.activeOrders.remove(orderId);
    if (removed == null) {throw new IllegalArgumentException("Order not found by orderId in active orders:" + orderId);
    }
    // 从 UserOrders 中删除:
    ConcurrentMap<Long, OrderEntity> uOrders = userOrders.get(removed.userId);
    if (uOrders == null) {throw new IllegalArgumentException("User orders not found by userId:" + removed.userId);
    }
    if (uOrders.remove(orderId) == null) {throw new IllegalArgumentException("Order not found by orderId in user orders:" + orderId);
    }
}

删除订单时,必须从 activeOrdersuserOrders中全部成功删除,否则会造成 OrderService 内部状态混乱。

最后,根据业务需求,我们加上根据订单 ID 查询、根据用户 ID 查询的方法:

// 根据订单 ID 查询 Order,不存在返回 null:
public OrderEntity getOrder(Long orderId) {return this.activeOrders.get(orderId);
}
// 根据用户 ID 查询用户所有活动 Order,不存在返回 null:
public ConcurrentMap<Long, OrderEntity> getUserOrders(Long userId) {return this.userOrders.get(userId);
}

整个订单子系统的实现就是这么简单。

下面是问题解答。

Order 的 id 和 sequenceId 为何不合并使用一个 ID?

订单 ID 是 Order.id,是用户看到的订单标识,而 Order.sequenceId 是系统内部给订单的定序序列号,用于后续撮合时进入订单簿的排序,两者功能不同。

可以使用一个简单的算法来根据 Sequence ID 计算 Order ID:

OrderID = SequenceID * 10000 + today("YYmm")

因为 SequenceID 是全局唯一的,我们给 SequenceID 添加创建日期的 ”YYmm” 部分,可轻松实现按月分库保存和查询。

参考源码

可以从 GitHub 或 Gitee 下载源码。

GitHub

小结

一个订单系统在内存中维护所有用户的活动订单,并提供删除和查询方法。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计3627字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中