tegg icon indicating copy to clipboard operation
tegg copied to clipboard

[RFC] 增强Controller能力与相关HttpMethod

Open Cyberhan123 opened this issue 3 years ago • 14 comments

  • 开始时间: 2021/12/16
  • RFC PR:
  • tegg Issue:

背景

参照: 提供更加简单化请求方法的修饰器的讨论

思路

所有Controller 类型会通过namespace进行区分

  • 为了方便区分ControllerRestControllerHttpControllerRpcControlle需要单独划分命名空间以方便归类。 将HttpController和简化后的Controller将会被划分到 http namespace, RestController 会被划分到rest namespace,RpcControlle会被划分到 rpc namespace。

  • 以下将通过import的形式具体描述:

    import {Controller, HttpController} from '@eggjs/tegg/http';
    import {RestController} from '@eggjs/tegg/rest';
    import {RpcController} from '@eggjs/tegg/rpc';
    

新增baseRoot

  • config新增baseRoot能力

    tegg:{
      baseRoot:string
    }
    
  • 通过这个字段可以全局调整路由根路径

    //config.default.ts
    tegg:{
      baseRoot:'/tegg'
    }
    
    //controller/fruitController.ts
    
    @RestController('/api/v1')
    //or Controller('/api/v1')
    class Fruit {
        // GET /api/v1/
        @List
        home() {
        }
    }
    

    此时路径会被修正到‘/tegg/api/v1'’

新增Controller

  • 所有的方法入参暂定为继承自HttpController:

    Controller(path?: string, protoName?: string, controllerName?: string);
    

新增Controller下Http Method修饰器

  • 新的Http Method 会跟 HTTPMethodEnum 的枚举保持一致,包含 GETPOSTPUTDELETEPATCHOPTIONSHEAD

  • 所有的方法入参暂时定为继承自HTTPMethod,以GET为例:

    Get(path?: string, priority?: number)
    
  • 与HTTPMethod不同的是Get第一个入参为可选,如果Get的path入参为空时, 可以通过检索Handler Function 名称自动匹配路径。 下面以GET为例:

    @Controller('/api/v1')
    class Fruit {
        // GET /api/v1/hello
        @Get()
        hello() {
        }
    }
    

    以下方代码为例子,如果要处理当前Controller的跟路径,请使用Get('/')

    @Controller('/api/v1')
    class Fruit {
      // GET /api/v1/
      @Get('/')
      home() {
      }
    }
    

新增RestController修饰

  1. 新增RestController修饰,这个修饰器为HttpController修饰器的子类,主要是HttpController的扩展
  2. 基于RestController实现相关Restful 请求特性。

RESTful的独有特性

  • 与常规HttpController不同,RestController将自动携带 Hypermedia, API,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。 比如访问 https://api.github.com/ 即可获得所有api 访问api.github.com/user,然后就得到了下面结果。

    {
      "message": "Requires authentication",
      "documentation_url": "https://docs.github.com/rest/reference/users#get-the-authenticated-user"
    }
    

具体能力

  • 配置baseRoot对RestController的会产生影响,通过这个字段可以全局调整RESTful根路径。

    //config.default.ts
    tegg:{
      baseRoot:'/tegg'
    }
    

    此时会将rest默认的/api前缀覆盖为/tegg

      //config.default.ts
      tegg:{
        baseRoot:'/'
      }
    

    此时会将rest默认的/api前缀覆盖为/

  • @RestController继承自HttpController,但是与Controller不同,如果path为缺省会根据当前的class name 并以复数的形式自动补全,会在当前根目录自动生成Hypermedia API。

    type RestControllerOptions {
       docURL?:string,
       version?:string,
       protoName?: string,
       controllerName?: string,
    }
    @RestController(path?: string, options?: RestControllerOptions)
    
    

    以下方代码为例子,我们没有填写任何参数并且没有配置baseRoot字段,那么就会指向/api/v1/fruits

    @RestController()
    class Fruit {
      // GET /api/v1/fruit/apple
      @List
       apple() {}
    }
    

    下面我们假设进行请求

    $ GET /api/v1/fruits
    # 我们会得到
    
    {
      "apple": "/api/v1/fruits/apple"
      "documentation_url": "/doc/v1/fruit"
    }
    
    $GET /api/v1 
    # 我们会得到
    
    {
      "fruit": "/api/v1/fruits"
      "documentation_url": "/doc/v1"
    }
    

    基于Get 请求增加,额外的业务能力

    @Query支持取出单条数据

     @Query()
     foo(id:string):Promise <Foo> {}
    

    @List 支持取出所有数据,默认遵从RESTful风格支持常见参数过滤(可能需要配合ORM的能力)

1.支持 ?limit=10:指定返回记录的数量

2.支持 ?offset=10:指定返回记录的开始位置。

3.支持 ?page=2&per_page=100:指定第几页,以及每页的记录数。 4.根据函数的入参可以进行横向扩展,比如需要支持搜索

@List()
listFoo(keyword:string):Promise<Array<Foo>> {}

剩余业务能力支持待补充

新增RpcController修饰

待调查: 主要调查方向GRPC, JSONRPC

其他资料与难点调研

英文单词复数化 hypermedia 实现调研

Cyberhan123 avatar Dec 16 '21 06:12 Cyberhan123

cc @killagu ,我把咱们讨论的问题进行了一下小结,在整理的时候发现了一点问题,我查了一下阮一峰的RESTful描述,如果要是严格按照这个描述的话,RestController的HttpMethod是否是一个就够了?我们是否要在这一个Handler里处理全部HttpMethod?

Cyberhan123 avatar Dec 16 '21 06:12 Cyberhan123

RestController 只有一个 http method 是不够的,每个动词都代表了不同的行为。不能混在一个 method 里的。

killagu avatar Dec 16 '21 15:12 killagu

@Controller('/api/v1')
class Fruit {
  // GET /api/v1/hello
  @Get()
  Hello() {}
}
  • Hello 方法名是否应该小写?
@Controller('/api/v1')
class Fruit {
  // GET /api/v1/
  @Get()
  Index() {}
}

Index 会被替换为 /。这个在方法名作为 path 的规则上又加了一个规则,有些复杂了,不如显示的写 @Get('/') 来的直接。

killagu avatar Dec 16 '21 16:12 killagu

@Controller('/api/v1')
class Fruit {
  // GET /api/v1/hello
  @Get()
  Hello() {}
}
  • Hello 方法名是否应该小写?
@Controller('/api/v1')
class Fruit {
  // GET /api/v1/
  @Get()
  Index() {}
}

Index 会被替换为 /。这个在方法名作为 path 的规则上又加了一个规则,有些复杂了,不如显示的写 @Get('/') 来的直接。

嗯,Hello的大小写我修改一下,第二个问题说的也有道理,确实是这样

Cyberhan123 avatar Dec 17 '21 01:12 Cyberhan123

RestController 只有一个 http method 是不够的,每个动词都代表了不同的行为。不能混在一个 method 里的。

这个能不能具体说一下,因为我平时使用RESTful比较少

Cyberhan123 avatar Dec 17 '21 01:12 Cyberhan123

rest 是以资源为维度来访问的,比如 POST 是创建一个资源, GET / 获取列表, GET /:id 是获取一个字端,PUT, DELETE 都有其含义。

killagu avatar Dec 20 '21 02:12 killagu

rest 是以资源为维度来访问的,比如 POST 是创建一个资源, GET / 获取列表, GET /:id 是获取一个字端,PUT, DELETE 都有其含义。

如果是这样的话好像不需要RestController了吧,只需要把 path传入对应的HttpMethod就可以了

Cyberhan123 avatar Dec 20 '21 06:12 Cyberhan123

意义还是有的,也是简化。 比如说 list,get 可以写成

@RestController()
class Resource {
  @List()
  listFoo(): Promise<Array<Foo>> {}

  @Get()
  getFoo(id: string): Promise<Foo> {}
}

killagu avatar Dec 21 '21 04:12 killagu

我去学习了一下相关内容,补充了一下RFC内容,麻烦帮忙康康有没有啥问题@killagu :relaxed:

Cyberhan123 avatar Dec 21 '21 15:12 Cyberhan123

以下方代码为例子,我们没有填写任何参数并且没有配置baseRoot字段,那么就会指向/api/v1/fruit/apple

按照 rest 风格,这个作为 /api/v1/fruits 更好,应该以资源为核心,与方法名无关。

新增的 controller 注解,可以补充一下如何 import。

killagu avatar Dec 22 '21 03:12 killagu

@Cyberhan123 可以先来写一版 http 的。RFC 看起来不错

killagu avatar Dec 27 '21 02:12 killagu

@Cyberhan123 可以先来写一版 http 的。RFC 看起来不错

好的~~,这个RFC也会不断完善的

Cyberhan123 avatar Dec 27 '21 03:12 Cyberhan123

最近比较忙,不过我会继续的,不会咕咕

Cyberhan123 avatar Jan 04 '22 01:01 Cyberhan123

没有进展了吗

yuu2lee4 avatar Apr 06 '23 05:04 yuu2lee4