当前位置 博文首页 > 、唐城:莎气藤藤YYDS!如何写出一手好的业务代码?

    、唐城:莎气藤藤YYDS!如何写出一手好的业务代码?

    作者:[db:作者] 时间:2021-08-20 19:05

    说明?

    这里举一个非常简单的例子,以案例的业务实现来分析如何写好业务代码。

    本案例只是简单的模拟,可能与真实的情况有出入,这里只是为了举例使用。

    案例:

    用户挑选商品放入购物车,然后下单结算,流程如下:

    1. 挑选商品

    2. 下单

    3. 结算

    4. 生成订单

    5. 通知

    提交下单的业务逻辑如下:

    1. 验证账号是否合法

    2. 调用第三方接口查看商品的打折价格

    3. 钱包金额扣除

    4. 生成订单信息

    5. 通知用户下单成功,等待收货

    代码实现

    @Service??
    public?class?OrderServiceImpl?implements?OrderService?{
    ????@Autowired??
    ????private?UserMapper userMapper;
    ????@Autowired??
    ????private?ProductMapper productMapper;
    ????@Autowired??
    ????private?OrderMapper orderMapper;
    ????@Autowired??
    ????private?KafkaTemplate kafkaTemplate;
    ??
    ????/**
    ?????* 购买商品,提交订单
    ?????*?@param?userId 用户ID
    ?????*?@param?productId 商品ID
    ?????*?@return??
    ?????*/??
    ????public?Result?submit(Long userId, Long productId)?throws?BizException?{
    ????????// 验证账号
    ????????UserDO userDO = userMapper.findById(userId);
    ????????if?(userDO ==?null) {
    ????????????throw?BizException(USER_NOT_EXISTS);
    ????????}
    ????????// 查看商品信息及打折信息
    ????????ProductDO productDO = productMapper.findById(productId);
    ????????Double delta = HttpUtils.getDiscount(productId);
    ????????double?actualPayment = productDO.getPrice() - delta;
    ????????Money money = userDO.getMoney();
    ????????if?(actualPayment > money.getRemain()) {
    ????????????// 如果商品价格 - 优惠价格 > 用户钱包,则说明不够付
    ????????????return?Result.fail("余额不足");
    ????????}
    ????????// 钱包够付,扣除金额
    ????????double?remain = money.getRemain() - actualPayment;
    ????????money.setRemain(remain);
    ????????// 更新账号钱包余额
    ????????userMapper.update(userDO);
    ????????// 生成订单信息
    ????????OrderDO orderDO =?new?OrderDO();
    ????????orderDO.setUserId(userId);
    ????????orderDO.setProductId(productId);
    ????????orderMapper.save(orderDO);
    ????????// 通知用户订单已生成,等待收货
    ????????kafkaTemplate.send("orderTopic", orderDO);
    ????????return?Result.ok();
    ????}
    }

    上面代码写好了,而且可以实现相关功能,但是随着业务的迭代,可能会出现很多问题:

    可维护性差

    1. XxMapper是基于Mybatis实现数据操作层,也就把技术细节带入业务逻辑中了,如果技术实现变了(改为使用Hibernate,或Mybatis版本升级造成用法改变等),业务代码就得改变。

    2. XxDO 是和数据表绑定的,数据表结构变更等也会影响业务代码。

    3. 调用第三方API,直接在业务代码中调用HttpUtils完成,未来第三方API修改了方法签名或返回值,或改为了RPC接口,那么业务代码也会随着改变。

    4. 发送消息直接使用KafkaTemplate,如果技术选型变了要改为使用RocketMQ,那么业务代码还得变。

    可扩展性差

    1. 如果商品因为做活动又加了其他的优惠,或商品某一段时间不打折了,那么原有的代码就会重新改来改去;

    2. 业务逻辑和数据存储结构是强依赖的,数据存储结构的变化对业务的影响可想而知;

    可测试性差

    因为直接依赖了数据库,第三方接口,中间件,所以需要所有技术实现后才能进行测试,测试成本和时间都比较大。

    代码优化一

    我们上面说了,数据库操作不应该直接暴露在业务逻辑中,因此把数据库操作“隔离”开。

    public?interface?UserRepository?{
    ????User?findById(Long userId);
    }

    新增 XxRepository 接口,业务逻辑直接依赖接口/抽象,而不应该直接依赖实现。

    Repository 是数据仓库,不一定非得是 DB,也可以是其他的数据操作。

    Repository 返回的对象也不是 DO,与数据库结构无关。

    代码优化二

    DO 对象是只有 set、get 操作,没有其他行为,我们说这有时是一种贫血现象,会导致本该在业务领域实体中完成的事情散落到各个 Service 中,低内聚而且也不好维护。

    增加领域实体,相关行为直接在实体内完成(高内聚):

    public?class?Money?{
    ????private?double?remain;
    ????public?double?getRemain()?{
    ????????return?remain;
    ????}
    ????public?void?setRemain(double?remain)?{
    ????????this.remain = remain;
    ????}
    ????/**
    ?????* 扣费
    ?????*?@param?delta
    ?????*?@return??
    ?????*/??
    ????public?boolean?charge(double?delta)?{
    ????????if?(remain < delta) {
    ????????????return?false;
    ????????}
    ????????this.remain -= delta;
    ????????return?true;
    ????}
    }

    代码优化三

    第三方接口是不可靠的,方法签名或返回值或调用方式都有可能会变的,如果直接在业务中依赖,会对业务造成“腐蚀”,所以应该加一层适配层(也叫防腐层 ACL)。

    /**
    ?* 防腐层/适配层
    ?*/??
    @Service??
    public?class?PayServiceImpl?implements?PayService?{
    ???
    ????@Autowired??
    ????private?DiscountFacade discountFacade;
    ??
    ????/**
    ?????* 支付
    ?????*?@param?money
    ?????*?@param?product
    ?????*?@return??
    ?????*/??
    ????public?boolean?pay(Money money, Product product)?{
    ????????// 获取优惠
    ????????Double delta = discountFacade.getDiscount(product.getId());
    ????????// 扣除费用
    ????????return?money.charge(product.getPrice() - delta);
    ????}
    }

    代码优化四

    抽象中间件,不直接依赖具体的 MQ 实现

    public?interface?MessageProducer<T,?R>?{
    ????Result<R>?send(T message);
    }

    总结

    优化后的代码如下:

    @Autowired??
    private?UserRepository userRepository;
    @Autowired??
    private?ProductRepository productRepository;
    @Autowired??
    private?OrderRepository orderRepository;
    @Autowired??
    private?MessageProducer<Order,Result> messageProducer;
    @Autowired??
    private?PayService payService;
    ??
    /**
    ?* 购买商品,提交订单
    ?*?@param?userId 用户ID
    ?*?@param?productId 商品ID
    ?*?@return??
    ?*/??
    public?Result?submit(Long userId, Long productId)?throws?BizException?{
    ????// 验证
    ????User user = userRepository.findByUserId(userId);
    ????if?(user ==?null) {
    ????????throw?BizException(USER_NOT_EXISTS);
    ????}
    ????// 支付
    ????Product product = productRepository.findById(productId);
    ????boolean?f = payService.pay(user.getMoney(), product);
    ????if?(!f) {
    ????????return?Result.fail("费用扣除失败");
    ????}
    ????// 更新账户
    ????userRepository.update(user);
    ????// 生成订单信息
    ????Order order = OrderFactory.create(user, product);
    ????orderRepository.add(order);
    ????// 通知用户订单已生成,等待收货
    ????messageProducer.send(order);
    ????return?Result.ok();
    }

    代码不一定非常严谨,只是通过这一个简单的例子告诉大家实际工作中代码该怎么写,该遵循哪些目标:

    • 独立于框架:架构不应该依赖某个外部的库或框架,不应该被框架的结构所束缚。

    • 独立于 UI:前台展示的样式可能会随时发生变化(今天可能是网页、明天可能变成 console、后天是独立 app),但是底层架构不应该随之而变化。

    • 独立于底层数据源:无论今天你用 MySQL、Oracle 还是 MongoDB、CouchDB,甚至使用文件系统,软件架构不应该因为不同的底层数据储存方式而产生巨大改变。

    • 独立于外部依赖:无论外部依赖如何变更、升级,业务的核心逻辑不应该随之而大幅变化。

    • 可测试:无论外部依赖了什么数据库、硬件、UI或者服务,业务的逻辑应该都能够快速被验证正确性。

    ?

    ?

    ?五年从程序员到架构师!这是我见过史上最好的程序员职业规划

    (下一篇)?16 条 yyds 的代码规范

    40 个 SpringBoot 常用注解

    别慌,在Java面试的时候,面试官会这样问关于框架的问题?

    来源:www.toutiao.com/i6903053083555807752?

    cs