4.3 Oinone的分布式体验

在oinone的体系中分布式比较独特,boot工程中启动模块中包含就走本地,不包含就走远程,本文带您体验下分布式部署以及分布式部署需要注意点。

看下面例子之前先把话术统一下:启动或请求SecondModule代表启动或请求pamirs-second-boot工程,启动或请求DemoModule代表启动或请求pamirs-demo-boot工程,并没有严格意义上启动哪个模块之说,只有启动工程包含哪个模块。

一、构建SecondModule模块

Step1 构建模块工程

参考3.2.1【构建第一个Module】一文,利用脚手架工具构建一个SecondModule,记住需要修改脚本。

脚本修改如下:

#!/bin/bash

# 项目生成脚手架
# 用于新项目的构建

# 脚手架使用目录
# 本地 local
# 本地脚手架信息存储路径 ~/.m2/repository/archetype-catalog.xml
archetypeCatalog=local

# 以下参数以pamirs-second为例

# 新项目的groupId
groupId=pro.shushi.pamirs.second

# 新项目的artifactId
artifactId=pamirs-second

# 新项目的version
version=1.0.0-SNAPSHOT

# Java包名前缀
packagePrefix=pro.shushi

# Java包名后缀
packageSuffix=pamirs.second

# 新项目的pamirs platform version
pamirsVersion=4.6.0

# Java类名称前缀
javaClassNamePrefix=Second

# 项目名称 module.displayName
projectName=OinoneSecond

# 模块 MODULE_MODULE 常量
moduleModule=second_core

# 模块 MODULE_NAME 常量
moduleName=SecondCore

# spring.application.name
applicationName=pamirs-second

# tomcat server address
serverAddress=0.0.0.0

# tomcat server port
serverPort=8090

# redis host
redisHost=127.0.0.1

# redis port
redisPort=6379

# 数据库名
db=demo

# zookeeper connect string
zkConnectString=127.0.0.1:2181

# zookeeper rootPath
zkRootPath=/second

mvn archetype:generate \
  -DinteractiveMode=false \
  -DarchetypeCatalog=${archetypeCatalog} \
  -DarchetypeGroupId=pro.shushi.pamirs.archetype \
  -DarchetypeArtifactId=pamirs-project-archetype \
  -DarchetypeVersion=4.6.0 \
  -DgroupId=${groupId} \
  -DartifactId=${artifactId} \
  -Dversion=${version} \
  -DpamirsVersion=${pamirsVersion} \
  -Dpackage=${packagePrefix}.${packageSuffix} \
  -DpackagePrefix=${packagePrefix} \
  -DpackageSuffix=${packageSuffix} \
  -DjavaClassNamePrefix=${javaClassNamePrefix} \
  -DprojectName="${projectName}" \
  -DmoduleModule=${moduleModule} \
  -DmoduleName=${moduleName} \
  -DapplicationName=${applicationName} \
  -DserverAddress=${serverAddress} \
  -DserverPort=${serverPort} \
  -DredisHost=${redisHost} \
  -DredisPort=${redisPort} \
  -Ddb=${db} \
  -DzkConnectString=${zkConnectString} \
  -DzkRootPath=${zkRootPath}

图4-3-1 构建一个名为SecondModule的模块

脚步执行生成工程如下:

image.png

图4-3-2 SecondModule的工程结构

Step2 调整配置

修改application-dev.yml文件

修改SecondModule的application-dev.yml的内容

  1. base库换成与DemoModule一样的配置,配置项为:pamirs.datasource.base
pamirs:
  datasource:   
        base:
      driverClassName: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://127.0.0.1:3306/demo_base?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
      username: root # 数据库用户
      password: oinone # 数据库用户对应的密码
      initialSize: 5
      maxActive: 200
      minIdle: 5
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      asyncInit: true

图4-3-3 base库换成与DemoModule一样的配置

  1. 修改后端Server的启动端口号为9091
server:
  address: 0.0.0.0
  port: 9091
  sessionTimeout: 3600

图4-3-4 修改后端Server的启动端口号为9091

修改bootstrap.yml文件

  1. 设置dubbo序列化方式为pamirs,记得DemoModule也要改。
spring:
  profiles:
    active: dev
  application:
    name: pamirs-second

pamirs:
  default:
    environment-check: true
    tenant-check: true

---
spring:
  profiles: dev
  cloud:
    config:
      enabled: false
      uri: http://127.0.0.1:7001
      label: master
      profile: dev
    nacos:
      server-addr: http://127.0.0.1:8848
      discovery:
        enabled: false
        namespace:
        prefix: application
        file-extension: yml
      config:
        enabled: false
        namespace:
        prefix: application
        file-extension: yml

dubbo:
  application:
    name: pamirs-second
    version: 1.0.0
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: -1
    serialization: pamirs
  scan:
    base-packages: pro.shushi
  cloud:
    subscribed-services:

---
spring:
  profiles: test
  cloud:
    config:
      enabled: false
      uri: http://127.0.0.1:7001
      label: master
      profile: test
    nacos:
      server-addr: http://127.0.0.1:8848
      discovery:
        enabled: false
        namespace:
        prefix: application
        file-extension: yml
      config:
        enabled: false
        namespace:
        prefix: application
        file-extension: yml

dubbo:
  application:
    name: pamirs-second
    version: 1.0.0
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: -1
    serialization: pamirs
  scan:
    base-packages: pro.shushi
  cloud:
    subscribed-services:

图4-3-5 修改bootstrap.yml文件

Step3 构建一个RemoteTestModel

package pro.shushi.pamirs.second.api.model;

import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.base.common.CodeModel;

@Model.model(RemoteTestModel.MODEL_MODEL)
@Model(displayName = "远程测试模型",labelFields = "name")
public class RemoteTestModel extends CodeModel {

    public static final String MODEL_MODEL="second.RemoteTestModel";

    @Field(displayName = "name")
    private String name;

}

新建RemoteTestModel模型,用于远程调用体验:

图4-3-6 构建一个RemoteTestMode

Step4 新增SecondSessionHook

@Hook(module= SecondModule.MODULE_MODULE), 规定只有增对SecondModule模块访问的请求该拦截器才会生效,不然其他模块的请求都会被SecondSessionHook拦截。

package pro.shushi.pamirs.second.core.hook;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.meta.annotation.Hook;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.core.faas.HookBefore;
import pro.shushi.pamirs.meta.api.dto.fun.Function;
import pro.shushi.pamirs.second.api.SecondModule;

@Component
@Slf4j
public class SecondSessionHook implements HookBefore {
    @Override
    @Hook(priority = 1,module= SecondModule.MODULE_MODULE)
    public Object run(Function function, Object... args) {
        log.info("second hook");
        return function;
    }
}

图4-3-7 新增SecondSessionHook

Step5 新建RemoteTestModelAction

在SecondModule中新建RemoteTestModelAction,自定义RemoteTestModel模型的queryPage函数。方便debug,看看效果

package pro.shushi.pamirs.second.core.action;

import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.second.api.model.RemoteTestModel;

@Model.model(RemoteTestModel.MODEL_MODEL)
public class RemoteTestModelAction {

    @Function.Advanced(type= FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.API})
    public Pagination<RemoteTestModel> queryPage(Pagination<RemoteTestModel> page, IWrapper<RemoteTestModel> queryWrapper){
        return new RemoteTestModel().queryPage(page,queryWrapper);
    }

}

图4-3-8 新建RemoteTestModelAction

Step6 boot工程需要引入Oinone的RPC包

  1. 父pom的依赖管理中先加入pamirs-distribution-faas的依赖
<dependency>
  <groupId>pro.shushi.pamirs.distribution</groupId>
  <artifactId>pamirs-distribution-faas</artifactId>
  <version>${pamirs.boot.version}</version>
</dependency>

图4-3-9 父pom的依赖管理中先加入pamirs-distribution-faas的依赖

  1. 在pamirs-second-boot中增加入pamirs-distribution-faas的依赖
<dependency>
  <groupId>pro.shushi.pamirs.distribution</groupId>
  <artifactId>pamirs-distribution-faas</artifactId>
</dependency>

图4-3-10 在pamirs-second-boot中增加入pamirs-distribution-faas的依赖

  1. 为SecondApplication类增加类注解@EnableDubbo
@EnableDubbo
public class SecondApplication {
}

图4-3-11 为SecondApplication类增加类注解@EnableDubbo

Step7 启动SecondModule

别忘了启动指令要为INSTALL,参考DemoModule的启动说明。

Step8 Second工程本地mvn install,方便DemoModule包依赖

二、DemoModule模块准备

Step1 DemoModule引入Oinone的RPC包和Second的API包

  1. 父pom的依赖管理中先加入pamirs-second-api和pamirs-distribution-faas的依赖
<dependency>
  <groupId>pro.shushi.pamirs.second</groupId>
  <artifactId>pamirs-second-api</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

<dependency>
  <groupId>pro.shushi.pamirs.distribution</groupId>
  <artifactId>pamirs-distribution-faas</artifactId>
  <version>${pamirs.boot.version}</version>
</dependency>

图4-3-12 依赖管理增加对应依赖包配置

  1. 在pamirs-demo-api中增加入pamirs-second-api的依赖
<dependency>
  <groupId>pro.shushi.pamirs.second</groupId>
  <artifactId>pamirs-second-api</artifactId>
</dependency>

图4-3-13 增加pamirs-second-api的依赖配置

  1. 在pamirs-demo-boot中增加入pamirs-distribution-faas的依赖
<dependency>
  <groupId>pro.shushi.pamirs.distribution</groupId>
  <artifactId>pamirs-distribution-faas</artifactId>
</dependency>

图4-3-14 增加pamirs-distribution-faas的依赖配置

  1. 为DemoApplication类增加类注解@EnableDubbo
@EnableDubbo
public class DemoApplication {
}

图4-3-15 为DemoApplication类增加类注解@EnableDubbo

Step2 修改DemoModule定义

修改DemoModule的依赖注解,增加SecondModule.MODULE_MODULE

@Module(dependencies = {SecondModule.MODULE_MODULE})

图4-3-16 注解方式申明DemoModule对SecondModule的依赖

Step3 修改pamirs-demo-boot的bootstrap.yml文件

参考SecondModule修改dubbo的序列化方式为pamirs

Step4 修改pamirs-demo-boot的DemoApplication

为依赖模块配置扫描包路径,修改DemoApplication增加second的扫描包,在日常开发中小伙伴的应用肯定不是以pamirs开头,所以大家别忘了SpringBoot的基本配置。在3.2.1【构建第一个Module】一文也提到我们在启动工程中需要配置启动模块和依赖模块的扫描路径。

image.png

图4-3-17 修改DemoApplication中关于SpringBoot的扫描包路径

三、分布式部署体验

以上工作准备好以后,我们就可以通过DemoModule来远程调用SecondModule。

第一个Case(前端把请求分别打到DemoModule、SecondModule)

在日常研发中,不同模块的菜单整合也是非常常见的,比如把B模块的菜单挂在A模块中。这个Case就是在讲解如何做到跨模块的菜单整合

Step1 修改前端工程的vue.config.js

利用node的proxy,分别把DemoCore转发到8090端口,把SecondCore转发到8091端口,把默认其他模块转发到8090端口。localhost还是127.0.0.1跟浏览器地址框输入保持一致

const WidgetLoaderPlugin = require('@kunlun/widget-loader/dist/plugin.js').default;
const Dotenv = require('dotenv-webpack');

module.exports = {
  lintOnSave: false,
  configureWebpack: {
    module: {
      rules: [
        {
          test: /\.widget$/,
          loader: '@kunlun/widget-loader',
        },
      ],
    },
    plugins: [new WidgetLoaderPlugin(), new Dotenv()],
    resolveLoader: {
      alias: {
        '@kunlun/widget-loader': require.resolve('@kunlun/widget-loader'),
      },
    },
  },
  devServer: {
    port: 8080,
    disableHostCheck: true,
    progress: false,
    proxy: {
      'pamirs/DemoCore': {
        // 支持跨域
        changeOrigin: true,
        target: 'http://localhost:8090',
      },
      'pamirs/SecondCore': {
          // 支持跨域
        changeOrigin: true,
        target: 'http://localhost:8091',
      },
      pamirs: {
          // 支持跨域
        changeOrigin: true,
        target: 'http://localhost:8090',
      },
    },
  },
};

图4-3-18 修改前端工程的vue.config.js

Step2 修改DemoMenus

增加一个RemoteTestModel的管理入口,这里需要指定菜单的module为DemoModule,不然应用会切换到SecondModule

@UxMenu("远程模型")@UxRoute(model = RemoteTestModel.MODEL_MODEL,module= DemoModule.MODULE_MODULE) class RemoteTestModelMenu{}

图4-3-19 修改DemoMenus

Step3 重启前端应用看效果

  1. 点击远程模型菜单,可以正常进行增、删、改、查操作。

image.png

图4-3-20 示例效果

  1. SecondSessionHook起作用

“second hook”并打印出来,前端请求没有再进过DemoModule再远程调用SecondModule,而是直接打到了SecondModule上。

image.png

图4-3-21 SecondSessionHook对应日志

第二个Case(跨模块代理,自动走远程)

Step1 新建RemoteTestModelProxy

在DemoModule中新建RemoteTestModelProxy代理继承RemoteTestModel

package pro.shushi.pamirs.demo.api.proxy;

import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import pro.shushi.pamirs.second.api.model.RemoteTestModel;

@Model.model(RemoteTestModelProxy.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY)
@Model(displayName = "远程模型的代理",summary="远程模型的代理")
public class RemoteTestModelProxy extends RemoteTestModel {

    public static final String MODEL_MODEL="demo.RemoteTestModelProxy";

}

图4-3-22 新建RemoteTestModelProxy

Step2 修改DemoMenus

增加一个RemoteTestModelProxy的管理入口

@UxMenu("远程代理")@UxMenu.route(RemoteTestModelProxy.MODEL_MODEL) class RemoteTestModelProxyMenu{}

图4-3-23 增加一个RemoteTestModelProxy的管理入口

Step3 重启看效果

  1. 点击菜单【远程代理】

image.png

图4-3-24 示例效果

  1. 在SecondModule的RemoteTestModelAction中debug,会发现DemoModule会自动调用RemoteTestModel模型的queryPage。

image.png

图4-3-25 DemoModule远程调用RemoteTestModel模型的queryPage

  1. 但不走SecondSessionHook

“second hook”并没有打印出来,为什么?在3.4.3.2【面向切面-拦截器】一文中介绍到“不是前端直接发起的请求不会生效”,可能小伙伴有疑问,我不是前端点击的吗?是的,但经过DemoModule再调用SecondModule,系统会判定为后端调用。

第三个Case(远程调用自定义函数)

Step1 新增函数RemoteTestModelService和RemoteTestModelServiceImpl

SecondModule定义RemoteTestModelService,用于DemoModule的调用。SecondModule记得再次mvn install

package pro.shushi.pamirs.second.api.service;

import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.second.api.model.RemoteTestModel;

@Fun(RemoteTestModelService.FUN_NAMESPACE)
public interface RemoteTestModelService {

    String FUN_NAMESPACE ="second.RemoteTestModelService";
    @Function
    Pagination<RemoteTestModel> queryPage(Pagination<RemoteTestModel> page, IWrapper<RemoteTestModel> queryWrapper);
    @Function
    String hello(String name);
    @Function
    String remoteTest(RemoteTestModel remoteTest);
}

图4-3-26 新增RemoteTestModelService接口类

package pro.shushi.pamirs.second.core.service;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.second.api.model.RemoteTestModel;
import pro.shushi.pamirs.second.api.service.RemoteTestModelService;

@Fun(RemoteTestModelService.FUN_NAMESPACE)
@Component
public class RemoteTestModelServiceImpl implements RemoteTestModelService {

    @Override
    @Function
    public Pagination<RemoteTestModel> queryPage(Pagination<RemoteTestModel> page, IWrapper<RemoteTestModel> queryWrapper) {
        return new RemoteTestModel().queryPage(page,queryWrapper);
    }
    @Override
    @Function
    public String hello(String name) {
        return "hello" + name ;
    }

    @Override
    @Function
    public String remoteTest(RemoteTestModel remoteTest) {
        return "remoteTest" + remoteTest.getName() ;
    }
}

图4-3-27 新增RemoteTestModelServiceImpl实现类

Step2 新增RemoteTestModelProxyAction

  1. 新增RemoteTestModelProxyAction,并定义queryPage、hello、remoteTest等Action,方便前端点击测试

  2. 引入RemoteTestModelService,分别调用queryPage、hello、remoteTest方法

package pro.shushi.pamirs.demo.core.action;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.proxy.RemoteTestModelProxy;
import pro.shushi.pamirs.framework.faas.utils.ArgUtils;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;–
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.second.api.model.RemoteTestModel;
import pro.shushi.pamirs.second.api.service.RemoteTestModelService;

@Model.model(RemoteTestModelProxy.MODEL_MODEL)
@Component
@Slf4j
public class RemoteTestModelProxyAction {
    @Autowired
    private RemoteTestModelService remoteTestModelService;

    @Function.Advanced(type= FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.API})
    public Pagination<RemoteTestModelProxy> queryPage(Pagination<RemoteTestModelProxy> page, IWrapper<RemoteTestModelProxy> queryWrapper){

        Pagination<RemoteTestModel> pageConvert = ArgUtils.convert(RemoteTestModelProxy.MODEL_MODEL,RemoteTestModel.MODEL_MODEL,page);
        IWrapper<RemoteTestModel> queryWrapperConvert = ArgUtils.convert(RemoteTestModelProxy.MODEL_MODEL,RemoteTestModel.MODEL_MODEL,queryWrapper);
        Pagination<RemoteTestModel> resultTmp=  remoteTestModelService.queryPage(pageConvert,queryWrapperConvert);
        Pagination<RemoteTestModelProxy> result = ArgUtils.convert(RemoteTestModel.MODEL_MODEL,RemoteTestModelProxy.MODEL_MODEL,resultTmp);
        return result;
    }

    @Action(displayName = "hello")
    public RemoteTestModelProxy hello(RemoteTestModelProxy data){
        String hello = remoteTestModelService.hello(data.getName());
        log.info(hello);
        return data;
    }
    @Action(displayName = "remoteTest")
    public RemoteTestModelProxy remoteTest(RemoteTestModelProxy data){
        RemoteTestModel s = ArgUtils.convert(RemoteTestModelProxy.MODEL_MODEL,RemoteTestModel.MODEL_MODEL,data);
        String testRemote = remoteTestModelService.remoteTest(s);
        log.info(testRemote);
        return data;
    }
}

图4-3-28 新增RemoteTestModelProxyAction

Step3 重启看效果

点击remoteTest按钮看看效果

image.png

图4-3-29 示例效果

总结一、二 、三 Case

把SecondModule模块停掉,则上面一、二 、三 Case都会报错。如果DemoModule的pamirs-demo-boot中依赖加上pamirs-second-core,同时application-dev.yml文件中pamirs.boot.modules加上second_core的配置,则DemoModule和SecondModule部署在一起,再把前端node请求代理都转发到DemoModule模块上。则只需要启动pamirs-demo-boot就可以了。这个留给小伙伴们自己实验。

分布式不分布式只是部署方式的差异,代码层面没有差异,有了体感以后再去看2.4.1【Oinone独特性之单体与分布式的灵活切换】一文可能会有更深的体会。

注意点:

  1. 【编码】远程调用服务时要确保入参和服务端定义的一样,可以用平台提供ArgUtils.convert进行转换

  2. 【部署】分布式情况下部署要用ng或其他方式进行转发,针对前端发起的请求,根据请求url中带的模块信息转发到对应有启动该模块的boot应用中

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9310.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
史, 昂's avatar史, 昂数式管理员
Previous 2024年5月23日 am8:28
Next 2024年5月23日 am8:30

相关推荐

  • 通知类

    1.通知类 1.1 站内信 当流程节点需要向用户发送消息或提示时使用站内信这个节点动作。接收人的选取类似审批节点的审批人,通知内容必填,配置完成,保存即可。 节点触发后,接收人会在全渠道运营平台-工作台-我的消息中查看到站内信。 1.2 邮件 当需要向指定邮箱发送邮件时使用邮件这个节点动作。可直接输入邮箱号,或者按照员工、部门、角色来选择,给接收人登记的邮箱发送邮件,也可以选择模型中的邮箱来发送邮件,若人员重复时只会发送一次邮件。填写主题,填写正文时可以调用当前流程中的一些模型字段的变量。 1.3 短信 发送短信需要在工作流-系统设置-短信模板中创建一个短信模板,阿里云模版新增需要一段时间审核,审核通过后就可以正常使用短信节点。短信的接收人与邮件的操作方式一致,之后选择短信模板,并且确定好变量的对应关系即可。

    2024年6月20日
    2.7K00
  • 2.2 互联网架构作为最佳实践为何失效

    如果把互联网架构比作社会主义,Oinone就是也要做有中国特色的社会主义,才能符合国情。 随着业务和生态的发展,企业对效率、性能、体验和智能化等方面的要求越来越高,但很多企业的系统面临着严重的系统架构落后和系统间割裂等问题,这些问题导致原有系统在业务发展下面临着效率和性能的双重挑战。与此同时,互联网平台的技术水平远远领先于传统企业系统,但是是否可以直接将互联网架构照搬到企业数字化转型中呢?显然,这是不合适的,因为互联网架构在企业数字化转型中面临着许多水土不服的问题。本章节将结合互联网中台架构的发展,分析这些问题的原因。 借鉴互联网中台理念 我们要先看互联网架构的发展,是如何一步步到今天提的中台架构概念的,每一步又解决了什么具体问题,我们以阿里架构变迁史为例来看下(如下图2-2所示): 图2-2 阿里架构变迁史 在2009年,淘宝上线了五彩石项目,这标志着淘宝从单体应用向服务化应用的时代迈出了一步。那么,淘宝为什么要开发五彩石项目呢?因为当时淘宝面临两个非常严峻的问题,一个是性能问题,数据库连接不足,数据库成为了瓶颈;另一个是效率问题,当时淘宝有百余个研发人员,但核心系统只有一套测试、预发、线上环境,导致研发需求排队等待。在开始五彩石项目之前,淘宝还做了千岛湖项目,用来验证服务化架构的可行性,将用户中心独立出来。随后,淘宝开启了五彩石项目,目标是通过增加人力来提升效率,通过增加机器来提升性能。 随着淘宝的业务发展,他们又面临了一个问题:各个服务之间有很多重复的建设,效率低下。为了解决这个问题,淘宝开始从服务化转向平台化,并创立了“共享业务事业部”,将重复建设的公共业务分配给这个事业部,以避免成本浪费。这些公共业务包括商品平台、交易平台和结算平台等。平台化的目标是规避服务化没有规划导致的重复建设问题。 但是随着业务的快速发展,淘宝变成了一个拥有几十个事业部的巨型企业,而这带来了新的问题:效率问题。例如,如果需要在一个业务线上做出改动,需要与十几个平台进行沟通,这是非常低效的。同时,对于一个平台来说,需要面对来自不同事业部的需求,这需要平台研发人员具备理解和抽象所有业务线需求的能力,这让平台研发人员感觉回到了单体应用时代,所有的需求都要排队,即使增加人力也无法提高效率。这个问题主要表现在交易平台上。 为了解决这个问题,淘宝提出了中台的概念,中台是在一套规范下建立的,让具有专业技能的团队自主决策业务系统发展的平台。中台的目标是弱化平台的业务特性,提供通用能力。简而言之,就是将“共享业务”中的“业务”两个字去掉,只提供通用能力的平台 我们将每个阶段的核心目标总结为一句话: 从单体到服务:通过增加人员和机器来提高效率和性能; 从服务化到平台化:解决服务化阶段因缺乏规划而导致的重复建设问题; 平台化到中台化:在一套规范下,让各业务团队自行决定业务系统发展,适用于多个业务线或多个场景应用的独立发展。 类似地,在企业数字化转型过程中,也面临着类似的问题: 随着企业业务在线化,对系统性能和稳定性提出了更高的要求,但由于内部系统之间的割裂,导致很多重复建设。因此,我们需要进行服务化和平台化; 没有一个供应商能够解决企业所有的商业场景问题,所以需要多个供应商共同参与。我们可以将供应商类比为各业务线,在一套规范下让供应商或业务线自行决定业务系统的发展。 然而,阿里的中台架构方案并不能直接照搬到企业中。因为阿里的中台架构采用了平台共建模式,即让业务线基于平台设计的规范共同开发。这本质上还是平台主导模式,对企业来说历史包袱较大。在企业中,让不同背景的研发一起共建交易或商品平台是非常复杂的事情。平台化已经足够复杂,再加上共建会导致企业架构的负载过重,这对企业来说就不再是赋能,而是“内耗”。 互联网中台架构在企业实践中遇到的问题 在1.3《Oinone的生态思考》一文中,《与中台的渊源》部分提到,在阿里云为企业提供数字化项目时,客户经常会对以下三个问题提出质疑,这些问题非常突出: 1我们听说你们具备敏捷响应能力,但为什么改动需求如此缓慢?不仅所需时间更长,而且成本更高? 2我们听说你们有能力中心,但为什么当我们引入新供应商或开发新场景时,前期建立的能力中心无法支持我们? 3我们听说你们的性能很好,但为什么我们需要投入更多的物理资源来支持项目? 在探讨互联网架构的适用性时,我想提出以下两个问题: 1企业应用程序的性能问题是否与互联网平台公司遇到的性能问题相同? 2企业应用程序的开发效率问题是否与互联网平台公司遇到的效率问题相同? 通过比较企业和互联网之间的差异,我们可以了解水土不服的核心原因。 企业 互联网 企业IT组织能力无法与数字化转型的速度匹配,缺乏足够的人才支持。为了提高开发效率,企业需要寻找工具和技术来降低开发难度,同时提高个人开发效率 互联网企业拥有众多优秀的人才,需要解决团队协作和知识共享的问题,即协同开发的效率。 企业无法制定并主导技术规范,这导致了能力复用的不足。为了提高效率和减少开发成本,企业需要建立统一的技术规范和标准,以便能力复用和组织协同。 互联网企业可以自定义技术规范,因此能力复用更易于保障。 企业往往当前业务量相对小,期望数字化建设能打动业务发展,对业务发展的预期比较高,所以企业的诉求是即满足当下成本效应又能兼顾未来对发展预期 互联网企业起步时的系统目标负载就高,通常会忽略资源起步门槛的问题,当然也可以通过自动扩容、云计算等方式来解决初期的负载问题。 表2-1从企业与互联网的对比,看水土不服的核心原因 我们可以看到企业和互联网架构在很多方面存在着不同的需求和问题。因此,在提供数字化服务时,Oinone需要注意与企业的组织能力进行匹配,并根据企业自身的特性来提供在线化的服务能力。这就像在社会主义制度下需要有中国特色一样,Oinone也需要有适合中国企业的特色。

    2024年5月23日
    2.2K00
  • 3.4.3.3 SPI机制-扩展点

    扩展点结合拦截器的设计,oinone可以点、线、面一体化管理Function 扩展点用于扩展函数逻辑。扩展点类似于SPI机制(Service Provider Interface),是一种服务发现机制。这一机制为函数逻辑的扩展提供了可能。 一、构建第一个扩展点 自定义扩展点(举例) 在我们日常开发中,随着对业务理解的深入,往往还在一些逻辑中会预留扩展点,以便日后应对不同需求时可以灵活替换某一小块逻辑。 在3.3.4【模型的继承】一文中的PetCatItemQueryService,是独立新增函数只作公共逻辑单元。现在我们给它的实现类增加一个扩展点。在PetCatItemQueryServiceImpl的queryPage方法中原本会先查询PetCatType列表,我们这里假设这个逻辑随着业务发展未来会发生变化,我们可以预先预留【查询萌猫类型扩展点】 Step1 新增扩展点定义PetCatItemQueryCatTypeExtpoint 扩展点命名空间:在接口上用@Ext声明扩展点命名空间。会优先在本类查找@Ext,若为空则往接口向上做遍历查找,返回第一个查找到的@Ext.value注解值,使用该值再获取函数的命名空间;如果未找到,则返回扩展点全限定类名。所以我们这里扩展点命名空间为:pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint 扩展点技术名称:先取@ExtPoint.name,若为空则取扩展点接口方法名。所以我们这里技术名为queryCatType package pro.shushi.pamirs.demo.api.extpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import java.util.List; @Ext public interface PetCatItemQueryCatTypeExtpoint { @ExtPoint(displayName = "查询萌猫类型扩展点") List<PetCatType> queryCatType(); } 图3-4-3-11 新增扩展点定义PetCatItemQueryCatTypeExtpoint Step2 修改PetCatItemQueryServiceImpl(用Ext.run模式调用) 修改queryPage,增加扩展点的使用代码。扩展点的使用有两种方式 方法一,使用命名空间和扩展点名称调用Ext.run(namespace, fun, 参数); 方法二,使用函数式接口调用Ext.run(函数式接口, 参数); 我们这里用了第二种方式 用PetCatItemQueryCatTypeExtpoint的全限定类名作为扩展点的命名空间(namespace) 用queryCatType的方法名作为扩展点的技术名称(name) 根据namespace+name去找到匹配扩展点实现,并根据规则是否匹配,以及优先级唯一确定一个扩展点实现去执行逻辑 package pro.shushi.pamirs.demo.core.service; ……省略依赖包 @Model.model(PetCatItem.MODEL_MODEL) @Component public class PetCatItemAction extends DataStatusBehavior<PetCatItem> { @Override protected PetCatItem fetchData(PetCatItem data) { return data.queryById(); } @Action(displayName = "启用") public PetCatItem dataStatusEnable(PetCatItem data){ data = super.dataStatusEnable(data); data.updateById(); return data; } @Function.Advanced(displayName = "查询模型数据的默认过滤条件", type = FunctionTypeEnum.QUERY, managed = true) @Function(openLevel = {LOCAL}) public String queryFilters() { StringBuilder sqlWhereCondition = new StringBuilder(); // List<PetCatType> typeList = new PetCatType().queryList(); List<PetCatType> typeList = Ext.run(PetCatItemQueryCatTypeExtpoint::queryCatType, new Object[]{}); if(!CollectionUtils.isEmpty(typeList)){ // sqlWhereCondition.append("type_id"); sqlWhereCondition.append(PStringUtils.fieldName2Column(LambdaUtil.fetchFieldName(PetCatItem::getTypeId))); sqlWhereCondition.append(StringUtils.SPACE).append(SqlConstants.IN).append(CharacterConstants.LEFT_BRACKET); for(PetCatType petCatType: typeList){ sqlWhereCondition.append(petCatType.getId()).append(CharacterConstants.SEPARATOR_COMMA); } sqlWhereCondition.deleteCharAt(sqlWhereCondition.lastIndexOf(CharacterConstants.SEPARATOR_COMMA)); sqlWhereCondition.append(StringUtils.SPACE).append(CharacterConstants.RIGHT_BRACKET); } return sqlWhereCondition.toString(); } ……省略其他函数 } 图3-4-3-12 修改PetCatItemQueryServiceImpl Step3 新增扩展点实现PetCatItemQueryCatTypeExtpointOne 扩展点命名空间要与扩展点定义一致,用@Ext(PetCatItemQueryCatTypeExtpoint.class) @ExtPoint.Implement声明这是在@Ext声明的命名空间下,且技术名为queryCatType的扩展点实现 package pro.shushi.pamirs.demo.core.extpoint; import pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint; import pro.shushi.pamirs.demo.api.model.PetCatType; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.meta.api.session.PamirsSession; import java.util.List; @Ext(PetCatItemQueryCatTypeExtpoint.class) public class PetCatItemQueryCatTypeExtpointOne implements PetCatItemQueryCatTypeExtpoint { @Override @ExtPoint.Implement(displayName = "查询萌猫类型扩展点的默认实现") public List<PetCatType> queryCatType() { PamirsSession.getMessageHub().info("走的是第一个扩展点"); List<PetCatType> typeList = new PetCatType().queryList(); return typeList; } } 图3-4-3-13 新增扩展点实现PetCatItemQueryCatTypeExtpointOne Step4…

    2024年5月23日
    1.9K00
  • 应用审计

    1. 整体介绍 应用审计是基于模型字段记录用户操作留痕记录,通过定义审计规则,平台基于审计规则订阅的字段记录形成日志。平台名词概念: 应用日志:针对已订阅的审计规则记录用户操作信息,是用户在各应用中操作行为留痕记录。 审计规则:业务审计中,数据变化订阅记录的规则。 操作入口:应用中心——业务审计应用。 2. 审计规则 审计规则是指定义审计过程订阅数据变化的信息,根据模型、订阅到具体字段内容变化形成应用日志。如订阅销售订单的备注,销售订单S20231001888,订单备注【尽快发货】,备注修改为【需易碎品包装】,审计规则为:销售订单模型,订阅【备注】。 操作包括:新增、编辑、删除。 2.1. 新增 新增时,定义审计规则名称,选择需要审计的模型,指定需要订阅的字段信息,同时可以指定关联关系的字段。 需要注意:每个模型仅限定义单条审计规则。 2.2. 编辑 编辑同新增操作,不做赘述。 2.3. 删除 删除规则后,平台将不再监听对应数据的变更日志,请慎重删除。 3. 应用日志 应用日志是针对已订阅的审计规则记录用户操作信息。记录操作用户、IP、登录设备、位置、订阅的字段变化内容。 应用日志详情 4. 业务表达 4.1. 展示效果 表格-行操作—日志记录 详情—日志记录 4.2. 操作步骤 Step1:在应用中心,需要维护业务应用依赖业务审计应用; 操作入口:应用中心,找到业务应用——编辑,依赖模块选择业务审计。 Step2:配置审计规则; 操作入口:业务审计应用——审计规则——新增规则。 Step2:界面设计器配置日志记录; 操作入口:界面设计器,找到需要配置的页面——模型组件,将动作区的日志记录拖动到页面中。

    2024年1月20日
    1.6K00
  • 4.1.20 框架之Session

    在日常开发中,我们经常需要把一些通用的信息放入程序执行的上下文中,以便业务开发人员快速获取。那么oinone的PamirsSession就是来解决此类问题的。 一、PamirsSession介绍 在oinone的体系中PamirsSession是执行上下文的承载,您能从中获取业务基础信息、指令信息、元数据信息、环境信息、请求参数,以及前后端MessageHub等。在前面的学习过程中我们已经多次接触到了如何使用PamirsSession: 在4.1.19【框架之网关协议-后端占位符】一文中,使用PamirsSession.getUserId()来获取当前登入用户Id,诸如此类的业务基础信息; 在4.1.18【框架之网关协议-variables变量】一文中,使用PamirsSession.getRequestVariables()得到PamirsRequestVariables对象,进而获取前端请求的相关信息; 在4.1.5【模型之持久层配置】一文中,使用PamirsSession.directive(),来操作元位指令系统,进而影响执行策略; 在4.1.13【Action之校验】、3.4.1【构建第一个Function】等文章中,都用到PamirsSession.getMessageHub()来设置返回消息。 二、构建模块自身Session(举例) 不同的应用场景对PamirsSession的诉求是不一样的,这个时候我们就可以去扩展PamirsSession来达到我们的目的 构建模块自身Session的步骤 构建自身特有的数据结构XSessionData 对XSessionData进行线程级缓存封装 利用Hook机制初始化XSessionData并放到ThreadLocal中 定义自身XSessionApi 实现XSessionApi接口、SessionClearApi。在请求结束时会调用SessionClearApi的clear方法 定义XSession继承PamirsSession 扩展PamirsSession的经典案例设计图 图4-1-20-1 扩展PamirsSession的经典案例设计图 构建Demo应用自身Session 下面的例子为给Session放入当前登陆用户 Step1 新建DemoSessionData类 构建自身特有的数据结构DemoSessionData,增加一个模型为PamirsUser的字段user,DemoSessionData用Data注解,注意要用Oinone平台提供的@Data package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.annotation.fun.Data; import pro.shushi.pamirs.user.api.model.PamirsUser; @Data public class DemoSessionData { private PamirsUser user; } 图4-1-20-2 新建DemoSessionData类 Step2 新建DemoSessionCache 对DemoSessionData进行线程级缓存封装 package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.service.UserService; public class DemoSessionCache { private static final ThreadLocal<DemoSessionData> BIZ_DATA_THREAD_LOCAL = new ThreadLocal<>(); public static PamirsUser getUser(){ return BIZ_DATA_THREAD_LOCAL.get()==null?null:BIZ_DATA_THREAD_LOCAL.get().getUser(); } public static void init(){ if(getUser()!=null){ return ; } Long uid = PamirsSession.getUserId(); if(uid == null){ return; } PamirsUser user = CommonApiFactory.getApi(UserService.class).queryById(uid); if(user!=null){ DemoSessionData demoSessionData = new DemoSessionData(); demoSessionData.setUser(user); BIZ_DATA_THREAD_LOCAL.set(demoSessionData); } } public static void clear(){ BIZ_DATA_THREAD_LOCAL.remove(); } } 图4-1-20-3 对DemoSessionData进行线程级缓存封装 Step3 新建DemoSessionHook 利用Hook机制,调用DemoSessionCache的init方法初始化DemoSessionData并放到ThreadLocal中。 @Hook(module= DemoModule.MODULE_MODULE), 规定只有增对DemoModule模块访问的请求该拦截器才会生效,不然其他模块的请求都会被DemoSessionHook拦截。 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.session.DemoSessionCache; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.core.faas.HookBefore; import pro.shushi.pamirs.meta.api.dto.fun.Function; @Component public class DemoSessionHook implements HookBefore { @Override @Hook(priority = 1,module = DemoModule.MODULE_MODULE) public Object run(Function function, Object… args) { DemoSessionCache.init(); return function; } } 图4-1-20-4 新建DemoSessionHook Step4 新建DemoSessionApi package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApi; import pro.shushi.pamirs.user.api.model.PamirsUser; public interface DemoSessionApi extends CommonApi { PamirsUser getUser(); } 图4-1-20-5 新建DemoSessionApi Step5…

    2024年5月23日
    2.1K00

Leave a Reply

Please Login to Comment