بسمه تعالی
هدف از این راهنما، ایجاد یک headless api در سیستم لایفری می باشد.
بنابراین از توضیحات اضافی در مورد مفاهیمی مانند Rest, API, ... اجتناب می شود.
توجه شود که در مراحلی که از طریق blade دستوری اجرا می شود، برای هماهنگی شدن intellij بایستی یک بار پروژه از طریق منوی زیر دوباره refresh شود.
بنابراین از توضیحات اضافی در مورد مفاهیمی مانند Rest, API, ... اجتناب می شود.
توجه شود که در مراحلی که از طریق blade دستوری اجرا می شود، برای هماهنگی شدن intellij بایستی یک بار پروژه از طریق منوی زیر دوباره refresh شود.
Gradle -> Reload All Gradle Projects
workspace name : vitamins
liferay version: portal-7.3-ga8
برای ایجاد workspace می توانیم از دو روش استفاده کنیم.
دستور زیر را در ترمینال اجرا کنید.
blade init -v portal-7.3-ga8 vitamins
در این روش ار طریق منوی زیر یک workspace با مشخصات ذکر شده ایجاد می کنیم.
File -> New -> Project -> Liferay -> Liferay Gradle Workspace
Package name: com.denbinger.vitamins
Template: rest-builder
Module name: headless-vitamins
cd modules
blade create -t rest-builder -p com.denbinger.vitamins headless-vitamins
از طریق منوی زیر ماژول مورد نظر را بسازید. توجه کنید که قالب rest-builder را انتخاب کنید.
File -> New -> Liferay Module
همان طور که مشاهده می کنید، داخل ماژول headless-vitamins چهار زیر ماژول api, client, impl, test ایجاد شده است.
برای پیاده سازی endpint ها فقط کافیست فایل rest-openapi.yaml را مطایق استاندارد OpenAPI ویرایش کنید.
برای این کار پیشنهاد می کنیم از swagger و یا پلاگین های مربوط به sswagger در intellij استفاده کنید تا درک آسان تری نسبت به مفاهیم موجود داشته باشید.
به صورت کلی، دو کامپوننت به نام ویتامین (Vitamin) و سازنده (Creator) داریم. فایل rest-openapi.yaml به صورت زیر خواهد بود.
Path های نیز سعی بر این شده تا حداکثر قوانین RestFul را رعایت کنند.
برای پیاده سازی endpint ها فقط کافیست فایل rest-openapi.yaml را مطایق استاندارد OpenAPI ویرایش کنید.
برای این کار پیشنهاد می کنیم از swagger و یا پلاگین های مربوط به sswagger در intellij استفاده کنید تا درک آسان تری نسبت به مفاهیم موجود داشته باشید.
به صورت کلی، دو کامپوننت به نام ویتامین (Vitamin) و سازنده (Creator) داریم. فایل rest-openapi.yaml به صورت زیر خواهد بود.
Path های نیز سعی بر این شده تا حداکثر قوانین RestFul را رعایت کنند.
info:
description: "HeadlessVitamins REST API"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
title: "HeadlessVitamins"
version: v1.0
openapi: 3.0.1
components:
schemas:
Vitamin:
description: Contains all of the data for a single vitamin or mineral.
properties:
name:
description: The vitamin or mineral name.
type: string
id:
description: The vitamin or mineral internal ID.
type: string
chemicalNames:
description: The chemical names of the vitamin or mineral if it has some.
items:
type: string
type: array
properties:
description: The chemical properties of the vitamin or mineral if it has some.
items:
type: string
type: array
group:
description: The group the vitamin or mineral belongs to, i.e. the B group or A group.
type: string
description:
description: The description of the vitamin or mineral.
type: string
articleId:
description: A journal articleId if there is a web content article for this vitamin.
type: string
type:
description: The type of the vitamin or mineral.
enum: [Vitamin, Mineral, Other]
type: string
attributes:
description: Health properties attributed to the vitamin or mineral.
items:
type: string
type: array
risks:
description: Risks associated with the vitamin or mineral.
items:
type: string
type: array
symptoms:
description: Symptoms associated with the vitamin or mineral deficiency.
items:
type: string
type: array
creator:
$ref: "#/components/schemas/Creator"
type: object
Creator:
description: Represents the user account of the content's creator/author. Properties follow the [creator](https://schema.org/creator) specification.
properties:
additionalName:
description: The author's additional name (e.g., middle name).
readOnly: true
type: string
familyName:
description: The author's surname.
readOnly: true
type: string
givenName:
description: The author's first name.
readOnly: true
type: string
id:
description: The author's ID.
format: int64
readOnly: true
type: integer
image:
description: A relative URL to the author's profile image.
format: uri
readOnly: true
type: string
name:
description: The author's full name.
readOnly: true
type: string
profileURL:
description: A relative URL to the author's user profile.
format: uri
readOnly: true
type: string
type: object
paths:
"/vitamins":
get:
operationId: getVitaminsPage
tags: ["Vitamin"]
description: Retrieves the list of vitamins and minerals. Results can be paginated, filtered, searched, and sorted.
parameters:
- in: query
name: filter
schema:
type: string
- in: query
name: page
schema:
type: integer
- in: query
name: pageSize
schema:
type: integer
- in: query
name: search
schema:
type: string
- in: query
name: sort
schema:
type: string
responses:
200:
description: ""
content:
application/json:
schema:
items:
$ref: "#/components/schemas/Vitamin"
type: array
application/xml:
schema:
items:
$ref: "#/components/schemas/Vitamin"
type: array
post:
operationId: postVitamin
tags: ["Vitamin"]
description: Create a new vitamin/mineral.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
responses:
200:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
"/vitamins/{vitaminId}":
get:
operationId: getVitamin
tags: ["Vitamin"]
description: Retrieves the vitamin/mineral via its ID.
parameters:
- name: vitaminId
in: path
required: true
schema:
type: string
responses:
200:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
put:
operationId: putVitamin
tags: ["Vitamin"]
description: Replaces the vitamin/mineral with the information sent in the request body. Any missing fields are deleted, unless they are required.
parameters:
- name: vitaminId
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
responses:
200:
description: Default Response
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
patch:
operationId: patchVitamin
tags: ["Vitamin"]
description: Replaces the vitamin/mineral with the information sent in the request body. Any missing fields are deleted, unless they are required.
parameters:
- name: vitaminId
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
responses:
200:
description: ""
content:
application/json:
schema:
$ref: "#/components/schemas/Vitamin"
application/xml:
schema:
$ref: "#/components/schemas/Vitamin"
delete:
operationId: deleteVitamin
tags: ["Vitamin"]
description: Deletes the vitamin/mineral and returns a 204 if the operation succeeds.
parameters:
- name: vitaminId
in: path
required: true
schema:
type: string
responses:
204:
description: ""
content:
application/json: {}
در این مرحله با اجرای buildRest از منوی Gradle کامپوننت ها و endpoint های تعریف شده ساخته می شوند.
توجه شود که buildRest را فقط برای ماژول impl اجرا کنید.
Gradle -> modules -> headless-vitamins -> headless-vitamins-impl -> Tasks -> build -> buildRest
توجه کنید که restBuild برای ایجاد کلاس ها، به قسمت tags
نباز دارد.
بعد از اجرای restBuild زیرماژول های api, test, impl, client کامل می شوند.
//todo complete explanation about api test impl client
در این مرحله بخش endpoint ها کامل شده است ولی برای برقراری ارتباط با دیتابیس و لایه ی persistence نیاز به یک service داریم.
در این مرحله برای تست بخش api، بعد از deploy کردم ماژول به طریقه ی زیر عمل می کنیم.
تست ارتباط با headless api به دو طریق انجام می شود. درخواستی مبنی بر دریافت مستندات api ارسال می کنیم. برای عبور از لایه امنیتی از یورز و پسورد ادمین استفاده میکنیم. توجه کنید که برای احراز هویت از استاندارد basicAuth استفاده می کنیم.
بعد از اجرای restBuild زیرماژول های api, test, impl, client کامل می شوند.
//todo complete explanation about api test impl client
در این مرحله بخش endpoint ها کامل شده است ولی برای برقراری ارتباط با دیتابیس و لایه ی persistence نیاز به یک service داریم.
در این مرحله برای تست بخش api، بعد از deploy کردم ماژول به طریقه ی زیر عمل می کنیم.
تست ارتباط با headless api به دو طریق انجام می شود. درخواستی مبنی بر دریافت مستندات api ارسال می کنیم. برای عبور از لایه امنیتی از یورز و پسورد ادمین استفاده میکنیم. توجه کنید که برای احراز هویت از استاندارد basicAuth استفاده می کنیم.
curl -u "LOGIN:PASSWORD" "http://localhost:8080/o/headless-vitamins/v1.0/openapi
.yaml"
که به جای LOGIN و PASSWORD
به ترتیب ایمیل و رمز عبور لایفری خود را وارد می کنید.
در خواستی از نوع GET و به آدرس زیر ارسال کرده و از تب Authorization
نوع احراز را Basic Auth انتخاب کنیدو نام کاربری و رمز عبور خود را وارد کنید.
اگر در هر یک از روش های بالا،داده های زیر را دریافت کردید، لایه ی headless api شما به درستی کار می کند.
اگر در هر یک از روش های بالا،داده های زیر را دریافت کردید، لایه ی headless api شما به درستی کار می کند.
openapi: 3.0.1
info:
title: HeadlessVitamins
description: HeadlessVitamins REST API
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: v1.0
servers:
- url: http://localhost:8080/o/headless-vitamins/
paths:
/v1.0/openapi.{type}:
get:
operationId: getOpenAPI
parameters:
- name: type
in: path
required: true
schema:
type: string
responses:
default:
description: default response
content:
application/json: {}
application/yaml: {}
برای ارتباط با دیتابیس، به یک persistence نیاز داریم(service layer) که به وسیله ی service builder ساخته می شود.
همانند ایجاد headless vitamins، از دو طریق می توان این ماژول را ایجاد کرد.
یک ماژول با مشخصات زیر ایجاد کنید.
Package name: com.denbinger.vitamins
Template: service-builder
Module name: vitamins
Hint !
With blade :
blade create -t service-bilder -p com.denbinger.vitamins vitamins
With Intellij
File -> new -> Liferay Module
در بخش شماتیک های کامپوننت ها در headless
مشاهده کردید که هر ویتامین دارای نام (name)، آیدی (id
)، نام های شیمیای (chemicalNames
) و ... می باشد. و همچنین ویتامین نیز خود بر یه نوع ویتامین(Vitamin
)، مینرال (Mineral) و دیگر(Other) می باشد.
در این قسمت برای ذخیره سازی این داده ها در دیتابیس و برای جلوگیری از زیاد شدن تعداد جدول ها، به صورت دیاگرام زیر عمل می کنیم.
در این قسمت برای ذخیره سازی این داده ها در دیتابیس و برای جلوگیری از زیاد شدن تعداد جدول ها، به صورت دیاگرام زیر عمل می کنیم.
همانطور که از پروژه ی guestbook به ساد دارید، service builder
برای ساخت کلاس های مورد نیاز، از فایل service.xml در زیر ماژول service
(در اینجا vitamins-service) استفاده می کند.
ستون های جدول persistedVitamin مطابق نمودار ER خواهد بود.
مرتب سازی داده ها بهتر است به صورت صعودی و بر اساس نام باشد.
برای دسترسی به داده های persistedVitamin به دو finder نیاز داریم. که اولی براساس آیدی جایگزین (surrogateId ) و دیگری بر اساس نام(name) باشد.
تا این مرحله، فایل service.xml به صورت زیر می باشد.
ستون های جدول persistedVitamin مطابق نمودار ER خواهد بود.
مرتب سازی داده ها بهتر است به صورت صعودی و بر اساس نام باشد.
برای دسترسی به داده های persistedVitamin به دو finder نیاز داریم. که اولی براساس آیدی جایگزین (surrogateId ) و دیگری بر اساس نام(name) باشد.
تا این مرحله، فایل service.xml به صورت زیر می باشد.
<?xml version="1.0"?><!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.3.0//EN"
"http://www.liferay.com/dtd/liferay-service-builder_7_3_0.dtd">
<service-builder dependency-injector="ds" package-path="com.denbinger.vitamins">
<namespace>FOO</namespace>
<entity local-service="true" name="PersistedVitamin" remote-service="true" uuid="true">
<!-- PK fields -->
<column name="persistedVitaminId" primary="true" type="long" />
<column name="surrogateId" type="String" />
<!-- Group instance -->
<column name="groupId" type="long" />
<!-- Audit fields -->
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="createDate" type="Date" />
<column name="modifiedDate" type="Date" />
<!-- Other fields -->
<column name="groupName" type="String" />
<column name="articleId" type="String" />
<column name="description" type="String" />
<column name="name" type="String" />
<column name="type" type="String" />
<!-- Order -->
<order by="asc">
<order-column name="name" />
</order>
<!-- Finder methods -->
<finder name="SurrogateId" return-type="PersistedVitamin">
<finder-column name="surrogateId" />
</finder>
<finder name="Name" return-type="PersistedVitamin">
<finder-column name="name" />
</finder>
</entity>
در ادامه نوبت به تعریف جدول VitaminDetail
می رسد. که مطابق نمودار تعریف خواهد شد.
برای دسترسی به داده های هر ویتامین (chemicalNames , properties , risks ,...) نیز دو finder بر اساس ستون persistedVitaminId و بر اساس persistedVitainId و type لازم داریم.
کد های زیر را به ادامه ی service.xml اضافه می کنیم و برای زیر ماژول vitamins-service ، از گریدل، buildService را اجرا می کنیم.
برای دسترسی به داده های هر ویتامین (chemicalNames , properties , risks ,...) نیز دو finder بر اساس ستون persistedVitaminId و بر اساس persistedVitainId و type لازم داریم.
کد های زیر را به ادامه ی service.xml اضافه می کنیم و برای زیر ماژول vitamins-service ، از گریدل، buildService را اجرا می کنیم.
<entity name="VitaminDetail" local-service="true" remote-service="true" uuid="true">
<column name="vitaminDetailId" primary="true" type="long" />
<column name="persistedVitaminId" type="long" />
<!-- Group instance -->
<column name="groupId" type="long" />
<!-- Audit fields -->
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="createDate" type="Date" />
<column name="modifiedDate" type="Date" />
<column name="type" type="String" />
<column name="value" type="String" />
<finder name="PersistedVitaminId" return-type="Collection">
<finder-column name="persistedVitaminId" />
</finder>
<finder name="persistedVitaminIdType" return-type="Collection">
<finder-column name="persistedVitaminId" />
<finder-column name="type" />
</finder>
</entity>
</service-builder>