黑马商城学习项目 02 - 微服务拆分

1. 熟悉黑马商城:先逛明白超市再开分店

想象黑马商城是家网红超市,刚开业时所有货架、收银台、仓库挤在一个大仓库里(单体架构)。咱们得先摸清楚各区域的位置和运作逻辑,不然以后拆分店(微服务)会乱成一锅粥。

启动前的 “密码破译”

启动项目前得改数据库配置,这就像超市开业前要拿到仓库钥匙:

1
2
3
4
hm:
db:
host: 192.168.150.101 # 虚拟机IP=仓库地址,别填成隔壁老王的
pw: 123 # 数据库密码=仓库门锁密码,丢了可就进不去了

还要在启动项里选 “local 环境”,相当于告诉系统:”我用自己的钥匙开门,不用备用钥匙”。

各功能区深度探秘

  • 登录系统:在 UserControllerlogin 方法里。就像超市的会员系统,你得刷脸(输入账号密码)才能解锁会员福利,后台会生成一个 “临时通行证”(token),拿着它才能逛付费区。
  • 商品搜索:首页搜索框对应 SearchControllersearch 方法。比如你搜 “螺蛳粉”,系统会在商品库里翻箱倒柜(数据库分页查询),把所有螺蛳粉摆到你面前,还贴心地分了页(怕你看不过来)。
  • 购物车操作CartController 管着所有 “装货、卸货、改数量” 的操作。就像你拿个购物篮,选了东西放进去(添加)、不想要了拿出来(删除)、觉得不够再拿一包(修改数量)。
  • 下单流程OrderControllercreateOrder 是核心。点击 “结算” 的瞬间,系统会干三件事:给你开一张购物小票(创建订单)、从仓库扣掉相应货物(减库存)、清空你的购物篮(清空购物车)。
  • 支付环节PayController 负责收钱。选 “余额支付” 后,系统先开个 “收款单”(支付流水),等你输完密码,就会扣掉余额、改收款单状态(已支付)、再把购物小票标成 “已付款”。

2. 服务拆分原则:分家得有章法

什么时候该分家?

  • 小项目初期:就像小夫妻刚结婚,住一居室(单体架构)足够了。省房租(开发成本)、收拾起来方便(维护简单),先试试日子能不能过下去(验证市场)。等生了娃(用户变多)、换了大 house(业务复杂),再考虑分房间(拆微服务)。
  • 大型项目开局:好比一开始就买了别墅(微服务架构),客厅、卧室、厨房早规划好。虽然装修费贵(前期投入大),但后期家人多了也不挤(可扩展性强),不用费劲改格局(避免后期拆分麻烦)。

怎么分才不打架?

  • 高内聚:一个服务只干一类事,就像厨房只做饭、卧室只睡觉。商品服务就管商品的增删改查、库存变动;订单服务就管订单创建、状态修改。不能让商品服务去操心订单怎么付款,不然就像让厨房兼做卧室,乱套!
  • 低耦合:服务之间少串门,非要交流就得有规矩。比如订单服务需要商品价格,不能直接闯进商品服务的仓库(查数据库),得按门铃(调用接口):”您好,麻烦告诉我这个商品的价格 ~”。商品服务也得守规矩,只要接口没变,内部怎么折腾(改代码)都不影响外面。

举个生动例子:

  • 纵向拆分:把超市分成 “生鲜区”(商品服务)、”收银台”(支付服务)、”储物柜”(购物车服务)。每个区域独立运营,各管一摊事。
  • 横向拆分:发现每个区域都需要 “打包袋”(发送消息),那就单独设个 “打包袋供应处”(消息服务),谁需要谁来领,不用每个区域自己生产袋子,省事儿!

3. 拆分购物车、商品服务:搬家打包指南

商品服务(item-service):给商品找个独立仓库

第一步:搭个新仓库(创建模块)

在父工程里建个叫 item-service 的模块,就像在超市旁边盖了个专门放零食的仓库。pom.xml 里得配齐家当(依赖):web 模块(接待客人)、数据库连接(开门钥匙)、mybatis(记账本),少一样都运转不起来。

第二步:写个仓库招牌(启动类)

1
2
3
4
5
6
7
@MapperScan("com.hmall.item.mapper") // 告诉系统账本在哪
@SpringBootApplication
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class, args); // 仓库开门营业
}
}

这就像在仓库门口挂个牌子:”商品仓库,正常营业”。

第三步:规划仓库布局(配置文件)

  • 端口设为 8081:相当于仓库门牌号,别人找得到
  • 数据库改名为 hm-item:专门放商品的账本,和其他账本分开
  • 其他配置照搬但微调:就像仓库的规章制度,大体和总超市一样,但细节有自己的特色

第四步:搬东西(拷贝代码)

把原来单体项目里和商品相关的 ControllerServiceMapper 都挪过来。注意!ItemServiceImpl 里有个 deductStock 方法,原来的 ItemMapper 地址变了,得重新指路(改包路径),不然系统会找不到账本。

第五步:录入账本(导入数据库)

执行 SQL 文件创建 hm-item 数据库,里面放着商品的所有信息(库存、价格、名称)。就像给新仓库建立单独的库存表,以后商品进出都在这记账。

验收:仓库试营业

启动服务后访问 http://localhost:8081/doc.html,用 swagger 测试 “批量查商品” 接口。输入几个商品 ID,能返回价格、库存,说明仓库运转正常,就像顾客问 “有没有可乐”,仓库能准确报出 “有,3 块钱一瓶,剩 100 瓶”。

购物车服务(cart-service):给购物篮建个管理处

流程和商品服务类似,但有两个小坑要填:

坑 1:暂时不知道用户是谁

原来的登录功能还在老超市(单体项目),购物车服务暂时认不出谁在操作,只能先写死一个用户 ID(比如 1L),就像暂时用 “临时访客” 身份记录购物篮。

坑 2:查不到商品价格

购物车需要显示商品价格,但商品已经搬到新仓库了。原来直接喊一声(本地调用)就能问到,现在得跨仓库沟通。暂时先把这部分代码注释掉,就像购物篮暂时只记 “买了啥”,不记 “多少钱”,等后面装了电话(服务调用)再补上。

4. 服务调用:给两个仓库装部电话

拆分后,购物车想知道商品价格,总不能让员工跑过去问吧?得装部电话(远程调用)。

RestTemplate:这电话很好用

在购物车服务里注册一个 RestTemplate,相当于装了部电话机:

1
2
3
4
5
6
7
@Configuration
public class RemoteCallConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(); // 电话机安装完成
}
}

打电话查价格:标准流程

修改 CartServiceImplhandleCartItems 方法,让购物车服务主动打电话问商品服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 先记下来要问哪些商品(收集商品ID)
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());

// 2. 拨号打电话
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}", // 商品仓库电话:8081分机
HttpMethod.GET, // 通话类型:咨询(GET)
null,
new ParameterizedTypeReference<List<ItemDTO>>() {}, // 要咨询的内容:商品信息
Map.of("ids", CollUtil.join(itemIds, ",")) // 具体要问的商品:1,2,3号商品
);

// 3. 记下来对方说的价格
if(response.getStatusCode().is2xxSuccessful()){
List<ItemDTO> items = response.getBody();
// 把价格填到购物篮里
for (CartVO v : vos) {
ItemDTO item = items.stream()
.filter(i -> i.getId().equals(v.getItemId()))
.findFirst()
.orElse(null);
if(item != null){
v.setNewPrice(item.getPrice()); // 终于知道价格了!
}
}
}

现在重启购物车服务,再查购物车,商品价格就出来了,就像购物篮上终于贴了价签。这时候,商品服务是 “被咨询的仓库”(服务提供者),购物车服务是 “打电话的咨询者”(服务消费者)。

5. 总结:拆家是为了更好地过日子

  • 拆分时机:小项目先凑活住(单体),人多了再分房(微服务);大项目一开始就规划好房间(微服务),免得后期拆墙(重构)。
  • 拆分技巧:每个服务专注一件事(高内聚),服务间少串门(低耦合),交流靠规矩(接口)。
  • 远程调用:就像跨部门沟通,用 RestTemplate 这部电话,按规矩拨号(HTTP 请求),就能问到想要的信息。

记住:微服务拆分不是为了炫技,而是为了让系统像乐高积木,哪个零件坏了换哪个,想加新功能直接加块积木,不用把整个玩具拆了重拼 ~