Skip to content
This repository has been archived by the owner on Nov 7, 2023. It is now read-only.

services.sql 和 Servie类 命名对应不上写的很痛苦 #22

Closed
cbqqkcel opened this issue Oct 23, 2022 · 33 comments
Closed

services.sql 和 Servie类 命名对应不上写的很痛苦 #22

cbqqkcel opened this issue Oct 23, 2022 · 33 comments

Comments

@cbqqkcel
Copy link

lealone的微服务和dubbo比确实好用,因为 dubbo 需要写定义文件。但是相对 spring cloud比,spring cloud是不需要写定义文件的。都是 java 类重构改名非常方便。

现在写 services.sql 主要是大小写下划线的问题,由于没有自动生成代码了。都是先写 Service类在写 services.sql 直接复制过来还得都改成下划线的,就非常痛苦。

@cbqqkcel
Copy link
Author

主要问题是开发体验感觉不太好

吊打dubbo+nacos。目前感觉没有spring colud+nacos好用(可能是还不太熟悉)

leanlone 只需要写 services.sql + service
spring could 只需要写 controller 和 service(全是java代码好重构)
spring could 单机不需要nacos,如果需要调用其他服务才需要 nacos+feign。如果被其他服务调用的情况下只需要 naocs

@codefollower
Copy link
Owner

codefollower commented Oct 23, 2022

lealone 一定要先写 services.sql,写完了执行一下,如果对应的 Service 类不存在会自动创建。

-- 创建服务: hello_service
create service if not exists hello_service (
  say_hello(name varchar) varchar -- HelloService 方法定义
)
implement by 'org.lealone.examples.rpc.HelloService' -- HelloService 默认实现类,不存在会自动创建

或者这样写 sql

-- 创建服务: user_service,会生成一个对应的UserService接口
create service if not exists user_service (
  add_user(name varchar, age int) long, -- 定义UserService接口方法 add_user
  find_by_name(name varchar) user -- 定义UserService接口方法 find_by_name
)
package 'org.lealone.examples.fullstack.generated.service' -- UserService接口所在的包名
implement by 'org.lealone.examples.fullstack.UserServiceImpl' -- UserService接口的默认实现类
generate code './src/main/java' -- UserService接口源文件的根目录

会生成一个 UserService 接口,你不用维护这个接口的,每次修改 services.sql 执行一下,都会生成新的 UserService 接口,
然后用 IDE 根据新的 UserService 接口自动重构老的 UserServiceImpl 类就可以了。

这种方式调用方不但可以通过 url 调用服务,还可以直接通过 UserService 接口调,性能也更好。
https://github.com/lealone/Lealone-Examples/tree/main/fullstack-demo 就是这么用的。

@codefollower
Copy link
Owner

易用性还可以有进一步的改进空间,比如不用在 create service 中敲服务的方法名了,自动通过反射现有的 java 类就可以了

-- 创建服务: hello_service
create service if not exists hello_service 
implement by 'org.lealone.examples.rpc.HelloService' -- 自动通过反射 HelloService 类查找它的可用 public 方法。

前题是通过反射要能拿到方法参数名,编译后的 class 文件不能把参数名信息去掉。

@cbqqkcel
Copy link
Author

返回值只能是string,基础类型加model吗,list可以吗

@codefollower
Copy link
Owner

支持的类型在这里
http://lealone.org/datatypes.html
别外 model 类也直接支持

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 23, 2022

不在 create service 中敲服务的方法名的话最后发现还不如用注解更方便。
我觉得最后还是会变成下面这个样子几乎和spring boot一样了

drop service if exists node_controller;
create service if not exists node_controller (
  get(id long) node,
  find_by_instance_id(instance_id long) varchar,
  delete_by_instance_id(instance_id long) void
)
implement by 'com.eesstudio.service.NodeController';

@RequiredArgsConstructor
public class NodeController {
    final NodeService nodeService;

    public String get(long id) {
        return nodeService.get(id).encode();
    }

    public String findByInstanceId(long instanceId) {
        return new JsonArray(nodeService.findByInstanceId(instanceId)).encode();
    }

    public void deleteByInstanceId(long instanceId) {
       nodeService.deleteByInstanceId(instanceId);
    }
}

public class NodeService {
    public Node get(long id) {
        return Node.dao.where().id.eq(id).findOne();
    }

    public List<Node> findByInstanceId(long instanceId) {
        return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findList();
    }

    public void deleteByInstanceId(long instanceId) {
        Node.dao.where().instanceId.eq(instanceId).delete();
    }
}

@codefollower
Copy link
Owner

用 javac 编译类或者在 eclipse 中编译类,默认都是没丢掉参数名的,你那个 Node 又是个 model 类,
像这么写就够了

drop service if exists node_Service;
create service if not exists node_Service
implement by 'com.eesstudio.service.NodeService';

public class NodeService {
    public Node get(long id) {
        return Node.dao.where().id.eq(id).findOne();
    }

    public List<Node> findByInstanceId(long instanceId) {
        return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findList();
    }

    public void deleteByInstanceId(long instanceId) {
        Node.dao.where().instanceId.eq(instanceId).delete();
    }
}

@cbqqkcel
Copy link
Author

用 javac 编译类或者在 eclipse 中编译类,默认都是没丢掉参数名的,你那个 Node 又是个 model 类, 像这么写就够了

drop service if exists node_Service;
create service if not exists node_Service
implement by 'com.eesstudio.service.NodeService';

public class NodeService {
    public Node get(long id) {
        return Node.dao.where().id.eq(id).findOne();
    }

    public List<Node> findByInstanceId(long instanceId) {
        return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findList();
    }

    public void deleteByInstanceId(long instanceId) {
        Node.dao.where().instanceId.eq(instanceId).delete();
    }
}

不配置方法会提示

java.lang.RuntimeException: no method: GET
at com.eesstudio.service.executor.NodeServiceExecutor.executeService(NodeServiceExecutor.java:29) ~[?:?]

@codefollower
Copy link
Owner

用现有的方案写你那个例子可以这么写

drop service if exists node_service;
create service if not exists node_service(
  get(id long) node,
  find_by_instance_id(instance_id long) array,
  delete_by_instance_id(instance_id long) void
)
implement by 'com.eesstudio.service.NodeService';

执行上面的 sql 后默认帮你创建的类是:

public class NodeService {
    public Node get(long id) {
        return null;
    }

    public Array findByInstanceId(long instanceId) {
        return null;
    }

    public void deleteByInstanceId(long instanceId) {
    }
}

然后你在里面填代码就好了,不用自己转 String。

@codefollower
Copy link
Owner

codefollower commented Oct 23, 2022

用 javac 编译类或者在 eclipse 中编译类,默认都是没丢掉参数名的,你那个 Node 又是个 model 类, 像这么写就够了

drop service if exists node_Service;
create service if not exists node_Service
implement by 'com.eesstudio.service.NodeService';

public class NodeService {
    public Node get(long id) {
        return Node.dao.where().id.eq(id).findOne();
    }

    public List<Node> findByInstanceId(long instanceId) {
        return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findList();
    }

    public void deleteByInstanceId(long instanceId) {
        Node.dao.where().instanceId.eq(instanceId).delete();
    }
}

不配置方法会提示

java.lang.RuntimeException: no method: GET at com.eesstudio.service.executor.NodeServiceExecutor.executeService(NodeServiceExecutor.java:29) ~[?:?]

还没有实现,只是说可以这么实现,但是这样做的话,别人就不知道怎么调用你的服务了,create service 其实就是给调用者提供一个接口规范,当然要根据你的 java 类反向生成完整的 create service 语句也是可以的,只是多了一些体力活而已。
create service 是支持多语言的,比如你可以把 implement by 'com.eesstudio.service.NodeService' 变成 implement by 'NodeService.js'

@cbqqkcel
Copy link
Author

cbqqkcel commented Oct 23, 2022

public Array findByInstanceId(long instanceId) {
      return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findArray();
  }

这样子前端会变成这个,还是得转换
image

@codefollower
Copy link
Owner

public Array findByInstanceId(long instanceId) {
      return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findArray();
  }

这样子前端会变成这个,还是得转换 image

这个可能是内部哪里编码多加了一点东西了,我晚点查查原因。

@cbqqkcel
Copy link
Author

services.sql 相当于一个接口规范+接口文档。
里面复杂类型还是需要其他工具配合生成文档。

@codefollower
Copy link
Owner

复杂类型你可以通过 create table xxx 创建,我们的项目一般都是把 create table xxx 和 create service yyy 全丢给调用方,然后他们通过 /service/yyy/method?p=v 这种标准格式调用就好了。

@codefollower
Copy link
Owner

codefollower commented Oct 23, 2022

后续再看看怎么支持 set<xxx>、list<xxx>、map<xxx,yyy> 这种 sql 语法,这种复杂类型一般数据库不太常支持。
如果用了 map<xxx,yyy> 调用方也不知道里面有什么 key

@cbqqkcel
Copy link
Author

后续再看看怎么支持 set、list、map<xxx,yyy> 这种 sql 语法,这种复杂类型一般数据库不太常支持。

是的,但是没有太难以接受了。

sql.Array 这个对象连 forEach 都没有,如果需要二次处理,要调用getResultSet(这个方法还抛SqlException)然后是一堆,字符串字段获取数据非常不利用重构。

@codefollower
Copy link
Owner

Array

java.sql.Array 设计得确实很糟糕,你只能 (Object[]) getArray()

@codefollower
Copy link
Owner

public Array findByInstanceId(long instanceId) {
      return Node.dao.where().instanceId.eq(instanceId).orderBy().sort.desc().findArray();
  }

这样子前端会变成这个,还是得转换 image

这个可能是内部哪里编码多加了一点东西了,我晚点查查原因。

这个问题已经修复

@cbqqkcel
Copy link
Author

static ManualInStockService create() {
        return create(null);
    }

    static ManualInStockService create(String url) {
        if (url == null)
            url = ClientServiceProxy.getUrl();

        if (ClientServiceProxy.isEmbedded(url))
            return new com.eesstudio.wms.admin.service.ManualInStockServiceImpl();
        else
            return new ServiceProxy(url);
    }

自动生成的 service 建议这里面的create改一个用户用不到的方法,防止冲突。
因为我们之前的规范是. crud 对应的 service 方法命名就是. create update delete find/get

@codefollower
Copy link
Owner

static ManualInStockService create() {
        return create(null);
    }

    static ManualInStockService create(String url) {
        if (url == null)
            url = ClientServiceProxy.getUrl();

        if (ClientServiceProxy.isEmbedded(url))
            return new com.eesstudio.wms.admin.service.ManualInStockServiceImpl();
        else
            return new ServiceProxy(url);
    }

自动生成的 service 建议这里面的create改一个用户用不到的方法,防止冲突。 因为我们之前的规范是. crud 对应的 service 方法命名就是. create update delete find/get

这个已经有考虑到了,后续提供一个参数在 create service 语句中加上 parameters(create_method_name='xxx') 就可以生成别的名字了。

@codefollower
Copy link
Owner

@qqcbqqkcel

代码已经提交,可以在 create service 语句最后加上 parameters(create_method_name='_create') 或别的名字。

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

set @packageName 'org.lealone.examples.hello.service.generated'; -- 生成的服务接口所在的包名

drop service if exists user_service;
create service if not exists user_service (
  create(user user) void,
  update(user user) void,
  delete(id long) void,
  get(id long) user
)
parameters(create_method_name='createService')
package @packageName
implement by 'org.lealone.examples.hello.service.UserServiceImpl' -- HelloService 接口的默认实现类
generate code @srcDir;


drop service if exists user_security_service;
create service if not exists user_security_service (
  get_user_security_by_user_id(user_id long) user_security,
  delete_user_security_by_user_id(user_id long) void,
)
parameters(create_method_name='createService')
package @packageName
implement by 'org.lealone.examples.hello.service.UserSecurityServiceImpl' -- HelloService 接口的默认实现类
generate code @srcDir;

这个下划线有配置参数吗(直接sql是啥代码里面就是啥不进行转换)
这个下划线已经给我看晕了,转来转去的。

@codefollower
Copy link
Owner

目前没有,因为 sql 的标识符直接转成 java 代码有可能是错的。
所以按惯例,sql 用下划线,java 用驼峰。
如果还考虑跟前端参数自动绑定的话,太灵活了反而头大,约定大于配置更省事。

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

create service if not exists user_security_service (
get_user_security_by_user_id(userId long) user_security,
delete_user_security_by_user_id(userId long) void,
)
可以入参可以驼峰,其他的又不行,不别扭吗

前端URL我也想驼峰访问,这样就统一了(URL,参数,SQL,Service 类全统一了)

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

如果不用自动生成service功能而是手写services.sql,还得搞个驼峰转下划线的工具快速转换。

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

自动生成代码这个在 idea 中不太好用,尤其是修改方法名时。idea 项目中任何代码有问题会导致 HelloSqlScriptTest 都无法运行了。需要手动删代码如何在改。

@codefollower
Copy link
Owner

用 set DATABASE_TO_UPPER false
然后别用下划线,这样 sql 里是啥 java 代码就是啥。

如果你一直遵循 sql 中的方法名和参数名都是下滑线,生成的 java 代码就是驼峰,当你需要改名字时,先改 sql 然后自动生成新的服务接口。如果你改 java 代码再改 sql 那用 sql 定义接口就没意义了。

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

发现还是有问题,如果是这种写法

  get_user_security_by_user_id(user_id long) user_security,
  delete_user_security_by_user_id(user_id long) void,

前端传参数必须用下划线了。这样不太好。(假如:我前端数据的字段都是驼峰,如果有自动传字段名的功能还得在前端把驼峰字段转换成下划线才行)

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

//第一种
export function getUserSecurityByUserId(userId) {
    return axios.post('/service/user_security_service/get_user_security_by_user_id', {user_id: userId})
}

//第二种
export function getUserSecurityByUserId(userId) {
    return axios.post('/service/userSecurityService/getUserSecurityByUserId', {userId})
}

前端使用如果是第一种真的很麻烦

@codefollower
Copy link
Owner

约定的惯例就是 sql 和 url 用下划线,js 和 java 的代码用驼峰。

@codefollower
Copy link
Owner

codefollower commented Jun 5, 2023

//第一种
export function getUserSecurityByUserId(userId) {
    return axios.post('/service/user_security_service/get_user_security_by_user_id', {user_id: userId})
}

//第二种
export function getUserSecurityByUserId(userId) {
    return axios.post('/service/userSecurityService/getUserSecurityByUserId', {userId})
}

前端使用如果是第一种真的很麻烦

或许你可以考虑用一下 lealone-rpc.js

var userService = lealone.getService("user_security_service");

export function getUserSecurityByUserId(userId) {
    return userService.getUserSecurityByUserId(userId)
}

lealone-rpc.js 就是封装了 axios

@codefollower
Copy link
Owner

如果你想所有地方都用驼峰,就用 set DATABASE_TO_UPPER false
然后别用下划线了,这样 sql 里是啥 java 代码就是啥,前端的方法名和参数名都是驼峰。
生成执行服务的代码就是这样:

    @Override
    public Object executeService(String methodName, Map<String, Object> methodArgs) {
        switch (methodName) {
        case "addUser":
            String p_name_1 = toString("name", methodArgs);
            Integer p_age_1 = toInt("age", methodArgs);
            return si.addUser(p_name_1, p_age_1);
        case "findByName":
            String p_name_2 = toString("name", methodArgs);
            return si.findByName(p_name_2);
        default:
            throw noMethodException(methodName);
        }
    }

@cbqqkcel
Copy link
Author

cbqqkcel commented Jun 5, 2023

可以太爽了,我就是想把所有的代码改成和java一样驼峰。包括数据库字段

@cbqqkcel cbqqkcel closed this as completed Jun 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants