### frontend:
  - Routes.js (path: 'xxx**Win**' for Window component)
  - load js resources:
    ```javascript
    // option 1
    items: [
      Ext.create('App.test.TestPanel',{
        title: 'Title 1'
      }),
      {
        xtype: 'panel',
        title: 'Title 2'
      }
    ]

    // option 2
    requires: ['App.xxxx.XxxxPanel'],
    items: [{
      xtype: 'test.testPanel',
      title: 'Title 1'
    }]
    ```
  - get component e.g.
	  ```javascript
	  me.down('test\\.testPanel') //alias: 'widget.test.testPanel'
  - i18n: static/public/i18n/{locale}.json
	  ```javascript
	  // component level
	  bind: {
	    text: '{i18n.xx.xx}'
	  }
	  // program level
	  App.getI18n('xx.xx') // or App.getI18n().xx.xx
  - security
	  - App.security.getUser()
	  - App.security.isGranted(string | string[])
	  - App.security.isGrantedAll(string[])

### backend (draft):
  - vs code dev: example.launch.json
  - db migration: resources/db/changelog/changes/\*\* (support subfolder)
    - suggested that one changeset with one DDL
    - filename: {yyyyMMdd}\_{number}\_{name}/{number}_{name}.sql
    - filename 2(optional): {yyyyMMdd}\_{number}\_{name}/{number}\_{name}/{number}\_{name}.sql
    ```sql
    --liquibase formatted sql

    --changeset {name}:{id}
    --comment: remarks (optional)
    CREATE TABLE `tableName` (
      `id` INT PRIMARY KEY AUTO_INCREMENT,
      `created` DATETIME NOT NULL DEFAULT NOW(),
      `createdBy` VARCHAR(30),
      `version` INT NOT NULL DEFAULT 0,
      `modified` DATETIME NOT NULL DEFAULT NOW(),
      `modifiedBy` VARCHAR(30),
      `deleted` BOOLEAN NOT NULL DEFAULT FALSE,

      `column1` INT NOT NULL,
      `column2` VARCHAR(255)
    );
    ```
  - controller
	  - AbstractController
	  - api: /public/\*\* /protected/\*\*
	  - RequestBody Class (`@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc...)
      ```java
      @NotNull
      @Pattern(regexp = "^Y$|^N$")
      private String YesNo;
      ```
	  - authentication check: `@PreAuthorize`
    - common response class `DataRes`, `IdRes`, `RecrodsRes`, `SuccessRes`
    - RESTfull api (http method, http status)
      - http status:
        - 404: record not found
        - 409: record_line not found or record_line not under the record (relation check)
        - 422: something wrong, (e.g. qty not enough)
      - suggest /save api (also allowed for POST: new, PUT: update)
        1. POST /xxxx/save
        2. body:
        ```json
        {
          "id": 1, //update
          "xx": "xx",
          "updateLines": [
            {
              "id": null, // new
              "xx": "xx"
            },
            {
              // need a relation check
              "id": 1, //update
              "xx": "xx"
            }
          ],
          // need a relation check
          "deleteLineIds": [1, 2]
        }
        ```
    ```java
    //example
    @RestController
    @RequestMapping("/xxxx")
    public class ExampleController extends AbstractController {

      @PostMapping('/xxx') // @GetMapping @DeleteMapping @PutMapping @PatchMapping
      //@ResponseStatus(HttpStatus.CREATED)
      //@PreAuthorize("hasAuthority('XXX_MAINT')")
      public Xxxx api (@RequestBody @Valid ReqClass req) {
      }

      @GetMapping('/{id}')
      public Xxxx getXxx (@PathVariable int id) {
      }
    }
    ```
  - service
	  - AbstractService, AbstractIdEntityService, AbstractBaseEntityService
	  - `@Transactional(rollbackFor = Exception.class)` for write, read only not need transaction
  - dao
	  - interface
	  - AbstractDao
	  - [Query Creation](https://docs.spring.io/spring-data/jpa/docs/2.7.0/reference/html/#jpa.query-methods.query-creation)
	  - `@Query`
      ```java
      public Optional<User> findByUsernameAndDeletedFalse(String username);
      // same
      @Query("FROM #{#entityName} u WHERE u.deleted = FALSE AND u.username = :username")
      public Optional<User> findByUsername(@Param("username") String username);
      ```
  - entity
	  - IdEntity, BaseEntity
	  - validation: `@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc...
  - jdbcDao
    - reduce Map: queryForEntity, queryForList("SELECT xxx" , params, XxxRecord.class)
      -  [Class Object vs Hashmap](https://stackoverflow.com/questions/10258071/class-object-vs-hashmap)
  - reduce `@Autowired`
	  - use constructor
	  - try separate class (e.g. FileListController, FileDownloadController)
  - return Optional instead of null
	  - return Optional\<T\> : may null
	  - return T : must not null
  - i18n: i18n/messages_{locale(underscore)}.properties
    - `messageSource.getMessage("name", null, LocaleUtils.getLocale());`
  - reporting:
    - excel:
      - files:
        - excel/{filename}.xlsx
        - excel/{filename}_zh-TW.xlsx
        - excel/{filename}_zh-CN.xlsx
      - load Workbook: `ExcelUtils.loadTemplate("excel/{filename}");`
      - send out Workbook: `ExcelUtils.send(response, workbook, "filename"); // will output filename.xlsx`
    - jasper
      - files:
        - reports/{filename}.jrxml
        - reports/{filename}_zh-TW.jrxml
        - reports/{filename}_zh-CN.jrxml
      - compile: `JasperUtils.compile("reports/{filename}", params, new JRBeanCollectionDataSource(xxx), Map.of("subReport1","reports/{filename}"));`
      - send out: `JasperUtils.responsePdf(response, jRerpot, "filename"); // will output filename.pdf`
  - mobile:
    - login: POST /mobile/login
    - logout: POST /mobile/logout
    - call api: http headers with
      1. `X-ATT-DeviceId`
      2. `X-ATT-Access-Token`