Este tutorial fornece um guia completo para compreender e trabalhar com a Linguagem Específica de Domínio (NammaDSL) no NammaYatri. Abrange a criação de arquivos YAML, geração e compilação de código, bem como a sintaxe para definição de APIs e armazenamento.
Localização : Crie um arquivo YAML dentro da pasta spec
do módulo em que você está trabalhando. Por exemplo, se você estiver trabalhando no rider-app
, o caminho seria:
rider-platform/rider-app/spec
Dependendo do tipo de especificação que você está definindo, você pode colocar o arquivo dentro da API
ou da pasta Storage
.
Geração de código : Após definir os arquivos YAML, execute o seguinte comando para gerar o código:
, run-generator
Este comando gera arquivos Haskell Beam, de consulta e de domínio no diretório src-read-only
, bem como as consultas SQL.
Nota importante : Este comando irá gerar apenas os arquivos de especificações novos ou alterados. Isso é feito obtendo o hash atual do arquivo de especificações e comparando com o hash do arquivo do commit HEAD
, run-generator --all
use argumentos "--all" para gerar todos os arquivos de especificações
Compilação : Compile o código usando:
cabal build all
imports
: usado para importar tipos predefinidos.
importPackageOverrides
: Usado para substituir pacotes de importação.
module
: especifica o nome do módulo.
Nota: No caso de APIs de painel, todas as APIs do módulo possuem o mesmo prefixo de API, por padrão é module
convertido para camel case.
apiPrefix
: substitui o prefixo da API padrão para a API do painel principal e auxiliar (opcional, específico para o painel).
Nota: Valor vazio ""
permitido para apiPrefix
e helperApiPrefix
, significa que não há prefixo de API.
helperApiPrefix
: sobrescrever o prefixo da API para API do painel auxiliar (opcional, específico para painel).
types
: define os tipos de solicitação e resposta para suas APIs. Este campo é opcional. O mesmo que tipos complexos em DSL de armazenamento. Os tipos são definidos no seguinte formato:
{TypeName}:
- {field1}: {field1Type}
- {field2}: {field2Type}
- derive: {Any extra derivation}
Nota: A sintaxe antiga não preserva a ordem dos campos e será obsoleta.
{TypeName}:
{field1}: {field1Type}
{field2}: {field2Type}
derive: {Any extra derivation}
Os tipos de enum podem ser definidos como:
{TypeName}:
- enum: {enum1},{enum2}
- derive: {Any extra derivation}
Para criar newtype ou tipo em vez de dados, use recordType: NewType | Data (Default) | Type
{TypeName}:
- recordType: NewType
- {fieldName}: {fieldType}
- derive: {Any extra derivation}
{TypeName}:
- recordType: Type
- type: {fieldType}
Para criar uma instância HideSecrets padrão, use a palavra-chave derive
(específica para painel)
{TypeName}:
- {fieldName}: {fieldType}
- derive: "'HideSecrets"
apis
: contém todas as APIs no seguinte formato:
{httpMethod}
(método HTTP GET | POST | PUT | DELETE) endpoint
: caminho da API
/path1/path2/{pathParam1}/path3/{pathParam2}
Nota: Os tipos de parâmetros de caminho devem ser mencionados na parte de parâmetros abaixo.
name
: nome da API. Às vezes, duas APIs diferentes têm o mesmo nome de API gerado automaticamente a partir do caminho, portanto, pode ser substituído (opcional)
Nota: Tenha cuidado ao alterar apiName
para uma API já existente. Quando apName
for alterado, a geração de Endpoint
e UserActonType
também será alterada, os dados antigos deverão ser migrados da seguinte forma:
migrate:
endpoint: <oldEndpoint>
userActionType: <oldUserActionType>
Normalmente, <oldEndpoint>
e <oldUserActionType>
são os mesmos valores neste formato:
PROVIDER_MANAGEMENT/BOOKING/POST_BOOKING_CANCEL_ALL_STUCK
response
:
type
: Tipo de resposta request
:
type
: Tipo de solicitação (opcional) multipart
:
type
: Tipo de solicitação em caso de solicitação multipart (opcional) auth
: Método de autenticação (padrão: TokenAuth) Ver mais
query
: lista de parâmetros de consulta
- {queryParam1}: {queryParam1Type}
Nota: A sintaxe antiga não preserva a ordem dos parâmetros e será obsoleta.
{queryParam1}: {queryParam1Type}
mandatoryQuery
: Lista de parâmetros de consulta obrigatórios
- {mandatoryQueryParam1}: {mandatoryQueryParam1Type}
Nota: A sintaxe antiga não preserva a ordem dos parâmetros e será obsoleta.
{mandatoryQueryParam1}: {mandatoryQueryParam1Type}
params
: lista de parâmetros de caminho
{pathParam1}: {pathParam1Type}
{pathParam2}: {pathParam2Type}
headers
: lista de cabeçalhos
headers:
- {header1}: {headerType1}
- {header2}: {headerType2}
helperApi
: contém recursivamente a API auxiliar do painel no mesmo formato da API principal (opcional, específica para painel)
validation
: nome qualificado para função de validação de solicitação (opcional)
Exemplo:
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
: usado para importar tipos predefinidos. Ver mais{dataTypeName}
: especifica o nome do módulo. tableName
: nome opcional da tabela, leva o snake_case do dataTypeName
se não estiver definido.
fields
: lista todos os campos da tabela com tipo Haskell. Ver mais
constraints
: PrimaryKey | Chave Secundária | NãoNulo | AUTOINCREMENTO
importPackageOverrides
: Usado para substituir pacotes de importação.
types
: tipos definidos pelo usuário, semelhantes aos tipos de API. Ver mais
derives
: Substitui deriva do tipo de dados principal.
derives : " Show,Eq,Ord "
beamType
: tipo de feixe definido pelo usuário para um tipo de dados especificado. Ver mais
beamFields
: Altere o nome do campo de feixe definido pelo usuário ou use-o se quiser ter algo diferente no lado da viga. Ver mais
beamInstance
: Podemos mencionar a instância de feixe que precisamos usando este campo.
sqlType
: tipo SQL definido pelo usuário para um campo. Ver mais
default
: valor sql padrão para campos, se houver
fields :
scheduleTryTimes : ' [Int] '
tripCategory : Text
default :
tripCategory : " 'All' "
scheduleTryTimes : " '{1800, 900, 300}' "
queries
: todas as consultas do Beam para a tabela. Ver mais
cachedQueries:
consultas armazenadas em cache para a tabela. Ver mais
fromTType
: FromTType dos campos, se aplicável. Ver mais
toTType
: ToTType dos campos, se aplicável. Ver mais
excludedFields
: existem alguns campos comuns como comercianteId, comercianteOperatingCityId, criadoAt e atualizadoAt que são adicionados automaticamente no tipo de dados. Para removê-los, use isto.
excludedFields :
- merchantOperatingCityId
- merchantId
extraIndexes
: Quaisquer índices adicionais Ver mais
extraOperations
: Operações extras Ver mais
Você deve fornecer o nome do módulo nas importações
imports :
Merchant : Domain.Types.Merchant
FRFSSearch : Domain.Types.FRFSSearch
Nota: Esses tipos comuns são importados automaticamente, então você pode usá-los diretamente sem importar
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
Para alterar o pacote:
importPackageOverrides:
Domain.Types.DataType1: dashboard-api
Importação gerada em haskell:
import "dashboard-api" Domain.Types.DataType1
Às vezes, podemos precisar pular a substituição do pacote quando geramos no mesmo pacote. Então devemos especificar o mapeamento de pacotes nas configurações dhall
:
, _packageMapping =
[ { _1 = GeneratorType. API_TYPES , _2 = " dashboard-api " }
{ _1 = GeneratorType. SERVANT_API , _2 = " rider-app " }
]
Importação gerada em haskell para API_TYPES
:
import "this" Domain.Types.DataType1
Importação gerada em haskell para SERVANT_API
:
import "dashboard-api" Domain.Types.DataType1
Na seção de campo mencione o nome do campo e seu tipo de Haskell correspondente
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
Para tipos de campo Simples (que acabaram de ser importados), ele também será copiado no lado da viga, a menos que mencionemos um tipo de viga específico
Se o campo for um tipo complexo, ele será dividido recursivamente no lado do feixe, a menos que mencionemos um tipo de feixe específico.
No caso de Id, ShortId o Beam Type será considerado como Texto
Se quisermos o tipo de dados importados no lado do domínio e o Id correspondente no lado do feixe, usaremos as extensões WithId com a definição do tipo.
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: É usado quando desejamos o Id do tipo de dados importado como o Beam Field. Isso não cria o tipo de dados na consulta de criação de feixe.
WithCachedId: O mesmo que WithId, a única diferença é que a consulta create e findbyId é importada de Storage.CachedQuery.*
WithIdCreate , WithCachedIdCreate: Use quando for necessário criar o tipo de dados na consulta de criação. Importante: O tipo de dados importado deve ter a função create no arquivo de consulta correspondente. Exemplo:
fields :
fareParams : Maybe FareParameters|WithIdCreate
farePolicy : Maybe FarePolicy|WithCachedId
Consulta de criação gerada:
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
Conversões ToTType e FromTType geradas:
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
}
Sintaxe:
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)
Sintaxe da cláusula Where:
where:
- {operator1}:
- field1
- {operator2}:
- field2
- field3
- {operator3}:
- field4
- field5
Os campos usados em parâmetros e cláusulas where podem ser de 3 tipos:
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
Lista de operadores where:
Exemplo:
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
Consulta gerada:
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
]