商品详情页,其实就是查各种接口,然后集中对用户展示商品各种各类信息,最核心的就是商品信息,还包括关键信息诸如价格信息、优惠券、促销信息、配送信息,还有一些可有可无的内容,比如问答、评价等信息。
我们实践中对于可有可无的信息,都是通过单独接口查询来实现展示的。我们这里就不多赘述。
核心商品接口以及关键信息接口查询,我们要求是包装在一个大接口中一并返回,因为信息都很关键,单独查询会造成数据不同步显示,影响用户体验。因为上游接口非常多,之间的关系非常复杂,并且有的是具有依赖关系的接口,还要考虑接口速度,不能串行执行。这就要求我们需要在设计中进行流程编排,让串行、并行同时存在。
要达到以上的目的,我们一方面可以自己在代码中分析然后自己编程实现,这样的代码可能效率上和内存占用上会更友好,但是会出现代码乱,维护困难的情况。如果新加入接口,我们就得在一大堆代码中找到我们应该加在那里,是串行还是并行,怎么效率更高。这样显然不是个非常好的方案。
我们的解决方案是有一套流程编排的框架,可以解决让查询串行并行结合,各个接口前后依赖明确,这里我大概写个简陋的demo,来展示实现思路,抛砖引玉。
这里的例子是定义了一个人员的信息的查询接口,我们在里边需要用到流程编排,来填充人员的各种信息。
package com.example.springdemo.domain;
import lombok.Getter;
import lombok.Setter;
/**
* 这里定义了一个人的类,name是基础信息,只有知道是谁才能够去查他的各种信息,
* 查high 然后才可以判断是否可以打篮球 查钱才能确认是否可以买房
* 查high money facevalue 才可以确定是否是高富帅
*/
@Getter
@Setter
public class Person {
/**
* 人的基础信息名称
*/
private String name;
/**
* 人的基础信息身高
*/
private Integer high;
/**
* 有多少钱
*/
private Integer money;
/**
* 颜值
*/
private Integer faceValue;
/**
* 是否是高富帅
*/
private Boolean isGFS;
/**
* 能打篮球
*/
private Boolean canPlayBasketBall;
/**
* 能买房子
*/
private Boolean canBuyHouse;
}
简单解释下,我们的核心接口是填充人员姓名name,知道name之后,我们则可以去查询他的身高high、有多少钱money、颜值多高faceValue。这三个查询是严格依赖于name的,必须等到name接口返回,这几个才能查询。
再来看是否可以打篮球canPlayBasketBall属性,他就是严格依赖身高,如果高过一定数值才可以打。同理canBuyHouse是否可以买房,就严格依赖他的money值,这就有了两条串行线。Name->money->canBuyHouse,name->high->canPlayBasketBall,但是money和high的查询是可以并行的,canBuyHouse和canPlayBasketBall也是可以并行的。canBuyHouse和high也是可以并行的,这里就不穷举了。
再看是否高富帅isGFS这个属性,他是依赖money、high 和faceValue三个接口返回的。
画了一个简单的依赖图
接下来看代码实现,我定义了一个接口handler,每个查询都实现这个接口。
package com.example.springdemo.service;
import com.example.springdemo.domain.Person;
public interface IPersonHandler {
void doHand(Person person);
}
再来看具体实现类,这里写几个,sleep模拟rpc接口的耗时。接口之前的依赖关系通过注解定义。
package com.example.springdemo.service.impl;
import com.example.springdemo.domain.Person;
import com.example.springdemo.service.IPersonHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
public class PersonNameHandler implements IPersonHandler {
@Override
public void doHand(Person person) {
person.setName("yaoming");
log.info(Thread.currentThread().getName() + " ---PersonNameHandler");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.example.springdemo.service.impl;
import com.example.springdemo.common.Dep;
import com.example.springdemo.domain.Person;
import com.example.springdemo.service.IPersonHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
@Dep(parent = {PersonNameHandler.class})
public class PersonMoneyHandler implements IPersonHandler {
@Override
public void doHand(Person person) {
if ("yaoming".equals(person.getName())) {
person.setMoney(10000000);
}
log.info(Thread.currentThread().getName() + " ---PersonMoneyHandler");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.example.springdemo.service.impl;
import com.example.springdemo.common.Dep;
import com.example.springdemo.domain.Person;
import com.example.springdemo.service.IPersonHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
@Dep(parent = {PersonMoneyHandler.class})
public class PersonBuyHouseHandler implements IPersonHandler {
@Override
public void doHand(Person person) {
if (person.getMoney() != null && person.getMoney() > 5000000) {
person.setCanBuyHouse(true);
}
log.info(Thread.currentThread().getName() + " ---PersonBuyHouseHandler");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.example.springdemo.service.impl;
import com.example.springdemo.common.Dep;
import com.example.springdemo.domain.Person;
import com.example.springdemo.service.IPersonHandler;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
@Dep(parent = {PersonMoneyHandler.class, PersonHighHandler.class, PersonFaceValueHandler.class})
public class PersonGFSHandler implements IPersonHandler {
@Override
public void doHand(Person person) {
if (person.getMoney() != null && person.getMoney() > 5000000 && person.getHigh() != null && person.getHigh() > 190 &&
person.getFaceValue() != null && person.getFaceValue() > 70) {
person.setIsGFS(true);
}
log.info(Thread.currentThread().getName() + " ---PersonGFSHandler");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
依赖注解的定义,里边定义了依赖的类。
package com.example.springdemo.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @date 2022-02-19 02:18 2022-02-19 02:50
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dep {
Class[] parent();
}
以上是查询接口,我们需要写代码,将以接口进行流程编排。下面进行简单时实现。主要看test方法,test1可能会卡流程请忽
具体解释写在代码里了,以上就基本实现了一个简单的流程编排框架。
这个框架比较简陋,也有一定的缺点,比如开始把所有的任务都提交了再在线程中判断父级是否执行完毕,这样会造成线程池的浪费,不如父级执行完了再在父级线程中将子任务提交线程池。
还有一个缺点是类名的地方,在用代理类后class会变,还得注意读取方式