本教程提供了理解和使用 NammaYatri 中的领域特定语言 (NammaDSL) 的全面指南。它涵盖了 YAML 文件的创建、代码生成和编译,以及定义 API 和存储的语法。
位置:在您正在处理的模块的spec
文件夹中创建一个YAML 文件。例如,如果您正在开发rider-app
,则路径将是:
rider-platform/rider-app/spec
根据您定义的规范类型,您可以将文件放置在API
或Storage
文件夹中。
代码生成:定义YAML文件后,执行以下命令生成代码:
, run-generator
此命令在src-read-only
目录中生成 Haskell Beam、查询和域文件以及 SQL 查询。
重要提示:此命令将仅生成那些新的或更改的规范文件。这是通过获取规范文件的当前哈希值并与 HEAD 提交的文件哈希值进行比较来完成的
, run-generator --all
使用“--all”参数生成所有规范文件
编译:使用以下命令编译代码:
cabal build all
imports
:用于导入预定义类型。
importPackageOverrides
:用于覆盖导入包 查看更多
module
:指定模块的名称。
注意:对于仪表板 API,模块中的所有 API 都具有相同的 API 前缀,默认情况下module
会转换为驼峰式大小写。
apiPrefix
:覆盖主仪表板 API 和辅助仪表板 API 的默认 API 前缀(可选,特定于仪表板)。
注意: apiPrefix
和helperApiPrefix
允许使用空值""
,这意味着没有 API 前缀。
helperApiPrefix
:覆盖助手仪表板 API 的 API 前缀(可选,特定于仪表板)。
types
:定义 API 的请求和响应类型。该字段是可选的。与存储 DSL 中的复杂类型相同。类型按以下格式定义:
{TypeName}:
- {field1}: {field1Type}
- {field2}: {field2Type}
- derive: {Any extra derivation}
注意:旧语法不保留字段顺序,将被弃用。
{TypeName}:
{field1}: {field1Type}
{field2}: {field2Type}
derive: {Any extra derivation}
枚举类型可以定义为:
{TypeName}:
- enum: {enum1},{enum2}
- derive: {Any extra derivation}
要创建新类型或类型而不是数据,请使用recordType: NewType | Data (Default) | Type
{TypeName}:
- recordType: NewType
- {fieldName}: {fieldType}
- derive: {Any extra derivation}
{TypeName}:
- recordType: Type
- type: {fieldType}
要创建默认的 HideSecrets 实例,请使用derive
关键字(特定于仪表板)
{TypeName}:
- {fieldName}: {fieldType}
- derive: "'HideSecrets"
apis
:包含以下格式的所有 API:
{httpMethod}
(HTTP 方法 GET | POST | PUT | DELETE) endpoint
:API路径
/path1/path2/{pathParam1}/path3/{pathParam2}
注意:路径参数类型应在下面的参数部分中提及。
name
:API 名称。有时两个不同的 API 具有从路径自动生成的相同 API 名称,因此可以覆盖它(可选)
注意:更改现有 API 的apiName
时要小心。当apName
更改时, Endpoint
和UserActonType
生成也会更改,旧数据应迁移如下:
migrate:
endpoint: <oldEndpoint>
userActionType: <oldUserActionType>
通常, <oldEndpoint>
和<oldUserActionType>
是以下格式的相同值:
PROVIDER_MANAGEMENT/BOOKING/POST_BOOKING_CANCEL_ALL_STUCK
response
:
type
:响应类型request
:
type
:请求类型(可选) multipart
:
type
:多部分请求时的请求类型(可选) auth
: 身份验证方法(默认:TokenAuth) 查看更多
query
: 查询参数列表
- {queryParam1}: {queryParam1Type}
注意:旧语法不保留参数顺序,将被弃用。
{queryParam1}: {queryParam1Type}
mandatoryQuery
:强制查询参数列表
- {mandatoryQueryParam1}: {mandatoryQueryParam1Type}
注意:旧语法不保留参数顺序,将被弃用。
{mandatoryQueryParam1}: {mandatoryQueryParam1Type}
params
:路径参数列表
{pathParam1}: {pathParam1Type}
{pathParam2}: {pathParam2Type}
headers
: 标题列表
headers:
- {header1}: {headerType1}
- {header2}: {headerType2}
helperApi
:递归地包含与主 API 格式相同的仪表板助手 API(可选,特定于仪表板)
validation
:请求验证函数的限定名称(可选)
例子:
imports : {}
module : Sos
types :
SosRes :
- sosId : Id Sos
SosDetailsRes :
- sos : Maybe Sos
SosReq :
- flow : SosType
- rideId : Id Ride
SosUpdateReq :
- status : SosStatus
- comment : Maybe Text
apis :
# GET /sos/getDetails
- GET :
endpoint : /sos/getDetails/{rideId}
auth : TokenAuth
params :
rideId : Id Ride
response :
type : API.Types.UI.Sos.SosDetailsRes
# # POST /sos/{sosId}/status
- POST :
endpoint : /sos/{sosId}/status
auth : TokenAuth RIDER_TYPE
params :
sosId : Id Sos
request :
type : API.Types.UI.Sos.SosUpdateReq
response :
type : Kernel.Types.APISuccess.APISuccess
auth: ApiAuth DRIVER_OFFER_BPP_MANAGEMENT DRIVERS LIST
imports
:用于导入预定义类型。查看更多{dataTypeName}
:指定模块的名称。 tableName
:表的可选名称,如果未定义,则采用dataTypeName
的蛇形命名法。
fields
:列出 Haskell 类型表的所有字段。查看更多
constraints
:主键 |次要密钥 |不为空 |自动增量
importPackageOverrides
:用于覆盖导入包 查看更多
types
:用户定义的类型,类似于API类型。查看更多
derives
:覆盖主数据类型的派生。
derives : " Show,Eq,Ord "
beamType
:指定数据类型的用户定义光束类型。查看更多
beamFields
:用户定义的光束字段名称,如果您想在光束侧有不同的东西,请更改或使用它。查看更多
beamInstance
:我们可以使用此字段提及我们需要的梁实例 查看更多
sqlType
:字段的用户定义的 sql 类型。查看更多
default
:字段的默认 sql 值(如果有)
fields :
scheduleTryTimes : ' [Int] '
tripCategory : Text
default :
tripCategory : " 'All' "
scheduleTryTimes : " '{1800, 900, 300}' "
queries
:表的所有 Beam 查询。查看更多
cachedQueries:
表的缓存查询。查看更多
fromTType
:字段的 FromTType(如果适用)。查看更多
toTType
:字段的 ToTType(如果适用)。查看更多
excludedFields
:有一些常见字段,例如merchantId、merchantOperatingCityId、createdAt和updatedAt,这些字段是自动添加到数据类型中的。要删除它们,请使用它。
excludedFields :
- merchantOperatingCityId
- merchantId
extraIndexes
: 任何附加索引 查看更多
extraOperations
: 额外操作 查看更多
您必须在导入中提供模块名称
imports :
Merchant : Domain.Types.Merchant
FRFSSearch : Domain.Types.FRFSSearch
注:这些常用类型是自动导入的,无需导入即可直接使用
Text -> Kernel.Prelude
Maybe -> Kernel.Prelude
Double -> Kernel.Prelude
TimeOfDay -> Kernel.Prelude
Day -> Data.Time.Calendar
Int -> Kernel.Prelude
Bool -> Kernel.Prelude
Id -> Kernel.Types.Id
ShortId -> Kernel.Types.Id
UTCTime -> Kernel.Prelude
Meters -> Kernel.Types.Common
HighPrecMeters -> Kernel.Types.Common
Kilometers -> Kernel.Types.Common
HighPrecMoney -> Kernel.Types.Common
Seconds -> Kernel.Types.Common
imports:
DataType1: Domain.Types.DataType1
更改套餐:
importPackageOverrides:
Domain.Types.DataType1: dashboard-api
在 haskell 中生成导入:
import "dashboard-api" Domain.Types.DataType1
有时,当我们在同一个包中生成时,我们可能需要跳过包覆盖。然后我们应该在dhall
配置中指定包映射:
, _packageMapping =
[ { _1 = GeneratorType. API_TYPES , _2 = " dashboard-api " }
{ _1 = GeneratorType. SERVANT_API , _2 = " rider-app " }
]
在 haskell 中为API_TYPES
生成导入:
import "this" Domain.Types.DataType1
在 haskell 中为SERVANT_API
生成导入:
import "dashboard-api" Domain.Types.DataType1
在字段部分提及字段名称及其对应的 Haskell 类型
imports : {}
LmsModule :
tableName : lms_module
fields :
id : Id LmsModule
merchantOperatingCityId : Id MerchantOperatingCity
category : LmsCategory
createdAt : UTCTime
updatedAt : UTCTime
duration : Int
noOfVideos : Int
rank : Int
variant : Maybe Variant
moduleCompletionCriteria : ModuleCompletionCriteria
对于简单字段类型(刚刚导入),除非我们提到特定的光束类型,否则它也会被复制到光束侧
如果字段是复杂类型,那么它将在光束侧递归分割,除非我们提到特定的光束类型
如果是 Id、ShortId,Beam 类型将被视为文本
如果我们想要在域侧导入数据类型并在梁侧使用相应的Id ,则我们可以使用 WithId 扩展和类型定义。
fields :
field1 : Int
beamType :
field1 : Text
fields :
a : Int
b : Text
beamFields :
a : " aa "
b : " bb "
# SomeType = SomeType {
# integerValueInText :: Text,
# version :: Int
# }
import :
SomeType : Domain.Types.SomeType
Some :
fields :
id : Id Some
val : SomeType # See this is an imported type
beamFields :
val :
intValue : Int
intValueInText : Text
# We have to right the toTType and fromTType functions
toTType :
intValue : (Kernel.Prelude.read . Domain.Types.SomeType.integerValueInText)
intValueInText : Domain.Types.SomeType.integerValueInText
fromTType :
val : mkVal
data Some = Some
{ id :: Kernel.Types.Id. Id Domain.Types.Some. Some ,
val :: Domain.Types.SomeType. SomeType ,
merchantId :: Kernel.Prelude. Maybe ( Kernel.Types.Id. Id Domain.Types.Merchant. Merchant ),
merchantOperatingCityId :: Kernel.Prelude. Maybe ( Kernel.Types.Id. Id Domain.Types.MerchantOperatingCity. MerchantOperatingCity ),
createdAt :: Kernel.Prelude. UTCTime ,
updatedAt :: Kernel.Prelude. UTCTime
}
deriving ( Generic , Show , ToJSON , FromJSON , ToSchema )
data SomeT f = SomeT
{ id :: B. C f Kernel.Prelude. Text ,
intValue :: B. C f Kernel.Prelude. Int ,
intValueInText :: B. C f Kernel.Prelude. Text ,
merchantId :: B. C f ( Kernel.Prelude. Maybe ( Kernel.Prelude. Text )),
merchantOperatingCityId :: B. C f ( Kernel.Prelude. Maybe ( Kernel.Prelude. Text )),
createdAt :: B. C f Kernel.Prelude. UTCTime ,
updatedAt :: B. C f Kernel.Prelude. UTCTime
}
deriving ( Generic , B.Beamable )
beamInstance : MakeTableInstances
$ (mkTableInstances ''PersonT " person " )
beamInstance : MakeTableInstancesGenericSchema
$ (mkTableInstancesGenericSchema ''PersonT " person " )
beamInstance : MakeTableInstancesWithTModifier [("deviceOS", "device_o_s")]
$ (mkTableInstancesWithTModifier ''MetaDataT " meta_data " [( " deviceOS " , " device_o_s " )])
beamInstance : Custom mkCacParseInstace [[Table2],[Table3]]
$ (mkCacParseInstace ''MetaDataT [[ Table2 ], [ Table3 ]])
beamInstance : Custom mkCacParseInstace "table_name" [Table2] [Table3] [(a,b,c)]
beamInstance :
- MakeTableInstances
- Custom mkCacParseInstace [[Table2],[Table3]]
- Custom Tool.Something.mkSomething "abc" [(a,b,c)] [[a],[b],[c]]
$ (mkTableInstances ''PersonT " person " )
$ (mkCacParseInstace ''MetaDataT [[ Table2 ], [ Table3 ]])
$ ( Tool.Something. mkSomething ''MetaDataT " abc " [(a,b,c)] [[a],[b],[c]])
fields :
field1 : " [Int] "
sqlType :
field1 : " text[] "
toTType :
subscriberUrl : Kernel.Prelude.func1|I
gatewayUrl : showBaseUrlSimple # This function will be created in seperated file as it's not imported
registryUrl : showBaseUrl|M # This function will be created in seperated file as it's not imported
fromTType :
updatedAt : Kernel.Prelude.fromMaybe createdAt|I
isScheduled : makeSchelude
tripCategory : Kernel.Prelude.fromMaybe (Domain.Types.Common.OneWay Domain.Types.Common.OneWayOnDemandDynamicOffer)|I
fields :
isVerified : Bool
verificationUrl : Text
aadharId : Text
toTType :
isVerified : (K.B.verify aadharId verificationUrl)|E
toTType :
isVerified : K.B.verify isVerified aadharId verificationUrl)|EM
toTType :
isVerified : K.B.verify isVerified (K.B.C.isCorrectAadhar aadharId) verificationUrl)|EM
WithId:当我们想要导入数据类型的 Id 作为 Beam Field 时使用。这不会在创建波束查询中创建数据类型。
WithCachedId:与WithId相同,唯一的区别是create和findbyId查询是从Storage.CachedQuery导入的。*
WithIdCreate 、 WithCachedIdCreate:当需要在创建查询中创建数据类型时使用此选项。重要提示:导入的数据类型应在其相应的查询文件中具有创建函数示例:
fields :
fareParams : Maybe FareParameters|WithIdCreate
farePolicy : Maybe FarePolicy|WithCachedId
生成的创建查询:
create :: ( EsqDBFlow m r , MonadFlow m , CacheFlow m r ) => Domain.Types.Estimate. Estimate -> m ()
create tbl = do
Kernel.Prelude. whenJust tbl . fareParams Storage.Queries.FareParameters. create
createWithKV tbl
生成的 ToTType 和 FromTType 转换:
instance FromTType' Beam. Estimate Domain.Types.Estimate. Estimate where
fromTType' Beam. EstimateT { .. } = do
fareParams' <- maybe ( pure Nothing ) ( Storage.Queries.FareParameters. findById . Kernel.Types.Id. Id ) fareParamsId
farePolicy' <- maybe ( pure Nothing ) ( Storage.CachedQueries.FarePolicy. findById . Kernel.Types.Id. Id ) farePolicyId
pure $
Just
Domain.Types.Estimate. Estimate
{
fareParams = fareParams',
farePolicy = farePolicy'
}
instance ToTType' Beam. Estimate Domain.Types.Estimate. Estimate where
toTType' Domain.Types.Estimate. Estimate { .. } = do
Beam. EstimateT
{ Beam. fareParamsId = ( Kernel.Types.Id. getId . ( . id ) <$> ) fareParams,
Beam. farePolicyId = ( Kernel.Types.Id. getId . ( . id ) <$> ) farePolicy
}
句法:
queries:
{query function name}:
kvFunction: {kv function name}
params: [field1, field2 .. ] Array of field to be updated in update queries
where:
{where clause}
orderby: {field name} (optional)
Where 子句语法:
where:
- {operator1}:
- field1
- {operator2}:
- field2
- field3
- {operator3}:
- field4
- field5
params 和 where 子句中使用的字段可以有 3 种类型:
where:
not_eq
where:
not_eq:
- id: id2|B
myname|CS -> "myname"
123|CI -> 123
123|CS -> "123"
0.23|CD -> 0.23
"0.34"|CS -> "0.23"
true|CB -> True
Domain.Something.defaultValue|CIM -> Domain.Something.defaultValue (and Domain.Something is added to imports)
kvFunction: updateWithKV
params:
- status: newStatus
where:
eq:
- status: NEW|CIM
kvFunction: updateWithKV
params:
- status: Domain.Types.DataType.CONFIRMED|CIM
where:
eq:
- status: NEW|CIM
where:
and:
- eq:
- status: NEW|CIM
- id|B
where 运算符列表:
例子:
LmsModule :
fields :
id : Id LmsModule
category : LmsCategory
question : Question
field1 : Text
field2 : Text
field3 : Text
types :
LmsCategory :
enum : " Safety, Financial, Training "
QuestionType :
enum : " Maths, Physics, Chemistry "
derive : " Show "
Question :
question : Text
tp : QuestionType
derive : " Show,Eq "
queries :
findByComplexCondition :
kvFunction : findAllWithOptionsKV
where :
and :
- field1
- or :
- field2
- field3
- category
- question
orderBy : createdAt
updateQuestionById :
kvFunction : updateWithKV
params :
- question
where :
id
生成的查询:
findAllWithKV
[ Se. And
[ Se. Is Beam. field1 $ Se. Eq field1,
Se. Or
[ Se. Is Beam. field2 $ Se. Eq field2,
Se. Is Beam. field3 $ Se. Eq field3
],
Se. Is Beam. category $ Se. Eq category,
Se. Is Beam. questionQuestion $ Se. Eq $ Domain.Types.LmsModule. question question,
Se. Is Beam. questionTp $ Se. Eq $ Domain.Types.LmsModule. tp question
]
]
updateQuestionById question ( Kernel.Types.Id. Id id ) = do
_now <- getCurrentTime
updateWithKV
[ Se. Set Beam. questionQuestion $ Domain.Types.LmsModule. question question,
Se. Set Beam. questionTp $ Domain.Types.LmsModule. tp question,
Se. Set Beam. updatedAt _now
]
[ Se. Is Beam. id $ Se. Eq id
]