rickchengx commented on code in PR #15321:
URL: 
https://github.com/apache/dolphinscheduler/pull/15321#discussion_r1496789777


##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/v2/controller/ProjectV2Controller.java:
##########
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.v2.controller;
+
+import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant;
+import org.apache.dolphinscheduler.api.controller.BaseController;
+import org.apache.dolphinscheduler.api.dto.project.ProjectCreateRequest;
+import org.apache.dolphinscheduler.api.dto.project.ProjectUpdateRequest;
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ApiException;
+import org.apache.dolphinscheduler.api.v2.annotation.Auth;
+import org.apache.dolphinscheduler.api.v2.dto.QueryResult;
+import org.apache.dolphinscheduler.api.v2.service.ProjectServiceV2;
+import org.apache.dolphinscheduler.common.constants.Constants;
+import org.apache.dolphinscheduler.common.enums.AuthorizationType;
+import org.apache.dolphinscheduler.dao.entity.Project;
+import org.apache.dolphinscheduler.dao.entity.User;
+
+import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * project controller
+ */
+@Tag(name = "PROJECT_TAG")
+@RestController
+@RequestMapping("/v2/projects")
+public class ProjectV2Controller extends BaseController {
+
+    @Autowired
+    private ProjectServiceV2 projectService;
+
+    /**
+     * create project
+     *
+     * @param loginUser            login user
+     * @param projectCreateRequest projectCreateRequest
+     * @return ProjectResponse ProjectResponse
+     */
+    @Operation(summary = "create", description = "CREATE_PROJECT_NOTES")
+    @PostMapping(consumes = {"application/json"})
+    @ApiException(Status.CREATE_PROJECT_ERROR)
+    @Auth(permissionKey = ApiFuncIdentificationConstant.PROJECT_CREATE, 
authorizationType = AuthorizationType.PROJECTS_V2)
+    @ResponseStatus(HttpStatus.CREATED)
+    @ResponseBody
+    public Project createProject(@Parameter(hidden = true) 
@RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                 @Valid @RequestBody ProjectCreateRequest 
projectCreateRequest) {
+        return projectService.createProject(loginUser, 
projectCreateRequest.getProjectName(),
+                projectCreateRequest.getDescription());
+    }
+
+    /**
+     * update project
+     *
+     * @param loginUser        login user
+     * @param code             project code
+     * @param projectUpdateReq projectUpdateRequest
+     * @return result Result
+     */
+    @Operation(summary = "update", description = "UPDATE_PROJECT_NOTES")
+    @PatchMapping(value = "/{code}", consumes = {"application/json"})
+    @ApiException(Status.UPDATE_PROJECT_ERROR)
+    @Auth(permissionKey = ApiFuncIdentificationConstant.PROJECT_UPDATE, 
authorizationType = AuthorizationType.PROJECTS_V2, needCheckResource = true)
+    @ResponseStatus(HttpStatus.OK)
+    @ResponseBody
+    public Project updateProject(@Parameter(hidden = true) 
@RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                 @PathVariable("code") Long code,
+                                 @Valid @RequestBody ProjectUpdateRequest 
projectUpdateReq) {
+        return projectService.update(loginUser, code, 
projectUpdateReq.getProjectName(),
+                projectUpdateReq.getDescription());
+    }
+
+    /**
+     * query project details by project code
+     *
+     * @param loginUser login user
+     * @param code      project code
+     * @return project detail information
+     */
+    @Operation(summary = "queryProjectByCode", description = 
"QUERY_PROJECT_BY_ID_NOTES")
+    @Parameters({
+            @Parameter(name = "code", description = "PROJECT_CODE", schema = 
@Schema(implementation = long.class, example = "123456", required = true))
+    })
+    @GetMapping(value = "/{code}")
+    @ApiException(Status.QUERY_PROJECT_DETAILS_BY_CODE_ERROR)
+    @Auth(permissionKey = ApiFuncIdentificationConstant.PROJECT, 
authorizationType = AuthorizationType.PROJECTS_V2, needCheckResource = true)
+    @ResponseStatus(HttpStatus.OK)
+    @ResponseBody
+    public Project queryProjectByCode(@Parameter(hidden = true) 
@RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                      @PathVariable("code") long code) {
+        return projectService.queryByCode(loginUser, code);
+    }
+
+    /**
+     * delete project by code
+     *
+     * @param loginUser login user
+     * @param code      project code
+     * @return delete result code
+     */
+    @Operation(summary = "delete", description = "DELETE_PROJECT_BY_ID_NOTES")
+    @Parameters({
+            @Parameter(name = "code", description = "PROJECT_CODE", schema = 
@Schema(implementation = long.class, example = "123456", required = true))
+    })
+    @DeleteMapping(value = "/{code}")
+    @ApiException(Status.DELETE_PROJECT_ERROR)
+    @Auth(permissionKey = ApiFuncIdentificationConstant.PROJECT_DELETE, 
authorizationType = AuthorizationType.PROJECTS_V2, needCheckResource = true)
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    @ResponseBody
+    public Boolean deleteProject(@Parameter(hidden = true) 
@RequestAttribute(value = Constants.SESSION_USER) User loginUser,
+                                 @PathVariable("code") Long code) {
+        return projectService.deleteProject(loginUser, code);
+    }
+
+    /**
+     * query project list paging
+     *
+     * @param loginUser login user
+     * @param searchVal search value
+     * @param pageSize  page size
+     * @param pageNo    page number
+     * @return project list which the login user have permission to see
+     */
+    @Operation(summary = "queryProjectListPaging", description = 
"QUERY_PROJECT_LIST_PAGING_NOTES")

Review Comment:
   The `listXXX` API generally has two methods for pagination: Offset-based 
pagination or Cursor-based (token-based) pagination. Currently, DS uses the 
offset-based pagination (e.g., pageSize and pageNo). When designing the v2 API, 
I think we need to discuss what paging method to use.
   
   ### Offset-based Pagination
   Users can see page numbers directly on the web UI and jump between different 
pages. But there are 2 problems with this approach:
   * Data conflicts and loss: If a data item is inserted or deleted during 
paging, users may see duplicate data items on the next page, or data item may 
be lost.
   * Performance issue: When the offset is large, query efficiency will become 
lower
   
   ### Cursor-based Pagination
   Instead of relying on numeric offsets, cursor-based pagination uses a unique 
identifier or token to mark the position in the dataset. Token-based pagination 
does not have data conflict/loss or performance issue. And cloud vendors (e.g., 
AWS / Azure / GCP) also use cursor-based pagination extensively.
   
   ### An example of Cursor-based Pagination
   * Assume table `records` is sorted using `id` and `update_time`
   * Assume that a page returns 10 data items, then take the `id` and 
`update_time` of the last data item as the cursor (usually encoded instead of 
returned to the user in plain text).
   * The user uses the cursor obtained from the previous query to initiate a 
second paging query, and DS decodes it to obtain the `last_data.id` and 
`last_data.update_time` of the last piece of data in the previous query.
   ```
   SELECT *
   FROM
       records
   WHERE
       user_id = xxx
       AND (
           update_time < last_data.update_time
           OR ( update_time = last_data.update_time AND id < last_data.id )
       )
   ORDER BY
       update_time DESC, id DESC
   LIMIT 10;
   ```
   
   I prefer to use cursor-based pagination in the v2 API of DS since it does 
not have data conflict/loss or performance issue. And I don't think users have 
a strong need to jump directly to a specific page. I'll start a discussion on 
the mailing list about pagination in v2-API. Any comments or suggestions are 
welcome.
   
   Ref:
   [1] [Offset vs Cursor-Based Pagination: Which is the Right Choice for Your 
Project?](https://medium.com/@oshiryaeva/offset-vs-cursor-based-pagination-which-is-the-right-choice-for-your-project-e46f65db062f)
   [2] [Offset vs Cursor-Based Pagination: Choosing the Best 
Approach](https://medium.com/@maryam-bit/offset-vs-cursor-based-pagination-choosing-the-best-approach-2e93702a118b)
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@dolphinscheduler.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to