Este artigo visa orientar sobre o desenho e implementação de APIs TOTVS respeitando as orientações do guia de implementação de APIs, bem como o modelo de conteúdo definido nas transações de mensagem padronizada TOTVS.
Informações |
---|
As orientações presentes neste documento estão em constante ajuste. Por isso, consulte sempre esta página quando for modelar uma nova transação. |
Partindo de um modelo XML como base, veremos a seguir um exemplo de uma possível implementação de API usando segmentação do modelo de dados.
O modelo XML utilizado será Contract_2_000.xsd que está representado graficamente a seguir (clique na imagem para expandir).
Convertendo este modelo "como ele é" para o formato OpenAPI 3.0, teríamos o seguinte documento (alguns elementos previstos nos guias de implementação de APIs e mensagem padronizada - REST/JSON foram omitidos para melhor compreensão):
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "openapi": "3.0.0", (...), "components": { "schemas": { "Contract": { "type": "object", "properties": { "CompanyId": { "type": "string" }, "BranchId": { "type": "string" }, "CompanyInternalId": { "type": "string" }, "InternalId": { "type": "string" }, "ContractNumber": { "type": "string" }, "ContractReview": { "type": "string" }, "ProjectInternalId": { "type": "string" }, "BeginDate": { "type": "string", "format": "date-time" }, "FinalDate": { "type": "string", "format": "date-time" }, "CustomerCode": { "type": "string" }, "CustomerInternalId": { "type": "string" }, "ContractTotalValue": { "type": "number", "format": "float" }, "ContractTypeCode": { "type": "string" }, "ContractTypeInternalId": { "type": "string" }, "ListOfSheet": { "type": "array", "items": { "type": "object", "properties": { "SheetNumber": { "type": "string" }, "SheetTypePoperty": { "type": "string" }, "UnitPrice": { "type": "number", "format": "float" }, "SheetTotalValue": { "type": "number", "format": "float" }, "ListOfItem": { "type": "array", "items": { "type": "object", "properties": { "ItemCode": { "type": "string" }, "ItemInternalId": { "type": "string" }, "AccountantAcountCode": { "type": "string" }, "AccountantAcountInternalId": { "type": "string" }, "CostCenterCode": { "type": "string" }, "CostCenterInternalId": { "type": "string" }, "AccountingItemCode": { "type": "string" }, "AccountingItemInternalId": { "type": "string" }, "ClassValueCode": { "type": "string" }, "ClassValueInternalId": { "type": "string" }, "ItemQuantity": { "type": "number", "format": "float" }, "ItemUnitPrice": { "type": "number", "format": "float" }, "ItemTotalValue": { "type": "number", "format": "float" }, "PercentageOfDiscount": { "type": "number", "format": "float" } } } } } } } }, "description": "Contrato" } } } } |
Entretanto, utilizar o modelo desta forma tem vários problemas como, por exemplo, na modificação do contrato, onde teríamos que enviar também as páginas (Sheet) do contrato e os itens das páginas.
Por isso, a segmentação do modelo de dados é permitida, desde que mantenha a estrutura e atributos do modelo XML original.
Nosso modelo OpenAPI poderia ser segmentado em 3 submodelos:
Convertendo isso para o modelo OpenAPI, teríamos o seguinte, lembrando que elementos como as tags de documentação foram omitidos por questões didáticas.
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "openapi": "3.0.0", (...), "components": { "schemas": { "ContractModel": { "type": "object", "properties": { "CompanyId": { "type": "string" }, "BranchId": { "type": "string" }, "CompanyInternalId": { "type": "string" }, "InternalId": { "type": "string" }, "ContractNumber": { "type": "string" }, "ContractReview": { "type": "string" }, "ProjectInternalId": { "type": "string" }, "BeginDate": { "type": "string", "format": "date-time" }, "FinalDate": { "type": "string", "format": "date-time" }, "CustomerCode": { "type": "string" }, "CustomerInternalId": { "type": "string" }, "ContractTotalValue": { "type": "number", "format": "float" }, "ContractTypeCode": { "type": "string" }, "ContractTypeInternalId": { "type": "string" }, "ListOfSheet": { "type": "array", "items": { "$ref": "#/components/schemas/SheetModel" } } }, "description": "Contrato" }, "SheetModel": { "type": "object", "properties": { "SheetNumber": { "type": "string" }, "SheetTypePoperty": { "type": "string" }, "UnitPrice": { "type": "number", "format": "float" }, "SheetTotalValue": { "type": "number", "format": "float" }, "ListOfItem": { "type": "array", "items": { "$ref": "#/components/schemas/ItemModel" } } } }, "ItemModel": { "type": "object", "properties": { "ItemCode": { "type": "string" }, "ItemInternalId": { "type": "string" }, "AccountantAcountCode": { "type": "string" }, "AccountantAcountInternalId": { "type": "string" }, "CostCenterCode": { "type": "string" }, "CostCenterInternalId": { "type": "string" }, "AccountingItemCode": { "type": "string" }, "AccountingItemInternalId": { "type": "string" }, "ClassValueCode": { "type": "string" }, "ClassValueInternalId": { "type": "string" }, "ItemQuantity": { "type": "number", "format": "float" }, "ItemUnitPrice": { "type": "number", "format": "float" }, "ItemTotalValue": { "type": "number", "format": "float" }, "PercentageOfDiscount": { "type": "number", "format": "float" } } } } } } |
Consequentemente, esta segmentação será refletida nos endpoints das APIs. Tomando por base a divisão realizada, teríamos o seguinte template:
Bloco de código | ||
---|---|---|
| ||
/api/{produto}/{agrupador}/v2.000v1/contracts/{ContractUniqueId}/sheets/{SheetNumber}/items/{ItemCode} |
Neste template, temos as seguintes considerações:
Nos exemplos a seguir veremos a utilização dos possíveis endpoints e seus respectivos modelos:
Inclusão de um contrato de forma completa
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "CompanyId": "1", "BranchId": "1", "CompanyInternalId": "1", "InternalId": "1|1|1", "ContractNumber": "1", "ContractReview": "1", "ProjectInternalId": "1|1", "BeginDate": "2018-07-25T14:24:00", "FinalDate": "2019-07-25T14:24:00", "CustomerCode": "1", "CustomerInternalId": "1", "ContractTotalValue": 1.0, "ContractTypeCode": "1", "ContractTypeInternalId": "1", "ListOfSheet": [{ "SheetNumber": "1", "SheetTypePoperty": "1", "UnitPrice": 1.0, "SheetTotalValue": 1.0, "ListOfItem": [{ "ItemCode": "1", "ItemInternalId": "1|1", "AccountantAcountCode": "0001", "AccountantAcountInternalId": "1|0001", "CostCenterCode": "1", "CostCenterInternalId": "1|1", "AccountingItemCode": "111", "AccountingItemInternalId": "1|111", "ClassValueCode": "001", "ClassValueInternalId": "1|001", "ItemQuantity": 1.0, "ItemUnitPrice": 1.0, "ItemTotalValue": 1.0, "PercentageOfDiscount": 0.0 } ] } ] } |
Inclusão de um contrato sem folhas (se a regra de negócio permitir)
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "CompanyId": "1", "BranchId": "1", "CompanyInternalId": "1", "InternalId": "1|1|1", "ContractNumber": "1", "ContractReview": "1", "ProjectInternalId": "1|1", "BeginDate": "2018-07-25T14:24:00", "FinalDate": "2019-07-25T14:24:00", "CustomerCode": "1", "CustomerInternalId": "1", "ContractTotalValue": 1.0, "ContractTypeCode": "1", "ContractTypeInternalId": "1", "ListOfSheet": [] } |
Recuperando contratos (apenas cabeçalho, 1a página, até o limite de uma página)
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "hasNext": false, "items": [{ "_expandables": ["ListOfSheet"], "CompanyId": "1", "BranchId": "1", "CompanyInternalId": "1", "InternalId": "1|1|1", "ContractNumber": "1", "ContractReview": "1", "ProjectInternalId": "1|1", "BeginDate": "2018-07-25T14:24:00", "FinalDate": "2019-07-25T14:24:00", "CustomerCode": "1", "CustomerInternalId": "1", "ContractTotalValue": 1.0, "ContractTypeCode": "1", "ContractTypeInternalId": "1", "ListOfSheet": [] } ] } |
Recuperando um contrato, expandindo os atributos ListOfSheet e ListOfItem
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "_expandables": ["ListOfSheet"], "CompanyId": "1", "BranchId": "1", "CompanyInternalId": "1", "InternalId": "1|1|1", "ContractNumber": "1", "ContractReview": "1", "ProjectInternalId": "1|1", "BeginDate": "2018-07-25T14:24:00", "FinalDate": "2019-07-25T14:24:00", "CustomerCode": "1", "CustomerInternalId": "1", "ContractTotalValue": 1.0, "ContractTypeCode": "1", "ContractTypeInternalId": "1", "ListOfSheet": [{ "_expandable": ["ListOfItem"], "SheetNumber": "1", "SheetTypePoperty": "1", "UnitPrice": 1.0, "SheetTotalValue": 1.0, "ListOfItem": [{ "ItemCode": "1", "ItemInternalId": "1|1", "AccountantAcountCode": "0001", "AccountantAcountInternalId": "1|0001", "CostCenterCode": "1", "CostCenterInternalId": "1|1", "AccountingItemCode": "111", "AccountingItemInternalId": "1|111", "ClassValueCode": "001", "ClassValueInternalId": "1|001", "ItemQuantity": 1.0, "ItemUnitPrice": 1.0, "ItemTotalValue": 1.0, "PercentageOfDiscount": 0.0 } ] } ] } |
Incluindo uma folha no contrato
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "SheetNumber": "1", "SheetTypePoperty": "1", "UnitPrice": 1.0, "SheetTotalValue": 1.0, "ListOfItem": [{ "ItemCode": "1", "ItemInternalId": "1|1", "AccountantAcountCode": "0001", "AccountantAcountInternalId": "1|0001", "CostCenterCode": "1", "CostCenterInternalId": "1|1", "AccountingItemCode": "111", "AccountingItemInternalId": "1|111", "ClassValueCode": "001", "ClassValueInternalId": "1|001", "ItemQuantity": 1.0, "ItemUnitPrice": 1.0, "ItemTotalValue": 1.0, "PercentageOfDiscount": 0.0 } ] } |
Incluindo um item de uma folha do contrato
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "ItemCode": "1", "ItemInternalId": "1|1", "AccountantAcountCode": "0001", "AccountantAcountInternalId": "1|0001", "CostCenterCode": "1", "CostCenterInternalId": "1|1", "AccountingItemCode": "111", "AccountingItemInternalId": "1|111", "ClassValueCode": "001", "ClassValueInternalId": "1|001", "ItemQuantity": 1.0, "ItemUnitPrice": 1.0, "ItemTotalValue": 1.0, "PercentageOfDiscount": 0.0 } |
Eliminando um item de uma folha
Eliminando uma folha de um contrato
O versionamento de APIs que atuam sobre entidades com mensagem padronizada seguirá o padrão definido para pelo guia de APIs, sendo independente da versão da mensagem padronizada. Teremos evolução da versão da API sempre que houver mudança Entretanto, mudanças no modelo de dados , ainda que no Guia de APIs conste outra orientação.O padrão será a letra "v" + versão da transação. Por exemplo: v2.000implicam em mudança no contrato da API e, consequentemente, podem exigir alteração da versão da API.
Segundo o guia de APIs, a versão é composta do seguinte formato:
"v" + majorVersion + "." + minorVersion
onde majorVersion e minorVersion são números inteiros sequenciais, sem "zeros" complementares.
Exemplos de APIs versionadas:
No caso de APIs relacionadas a transações do tipo Request, considerar o exposto no tópico a seguir.
Quando a modelagem da API envolver uma operação a ser realizada numa entidade, deve-se ter em mente que existem mensagens padronizadas do tipo Request, que representam operações a realizar no sistema de destino.
Neste caso, há duas possibilidades de modelagem da API:
Modelar a operação vinculada a uma entidade de mensagem padronizada existente:
Consideremos a transação CancelRequest, versão 1.000. Esta transação representa o cancelamento de uma requisição, que é referenciada dentro da mensagem, junto com outras informações.
Uma possível API para esta transação seria:
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{
"Code": "10",
"RequestInternalId": "100",
"Type": "000",
"CancelDateTime": "2018-07-20T12:00:34-03:00",
"CancelReason": "Rejeitado",
"CancelRelatedRequests": ""
} |
As regras para versionamento das APIs, quando relacionadas a mensagem padronizada, são as seguintes:
Para tornar mais claro como versionar as APIs baseadas em mensagem padronizada, colocaremos a seguir um caso de uso.
Na versão inicial da API temos os seguintes endpoints, todos baseados na versão 1.000 da transação Request:
Bloco de código | ||||
---|---|---|---|---|
| ||||
GET /api/manufacturing/stock/v1/requests
GET /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests
PUT /api/manufacturing/stock/v1/requests/{requestId}
DELETE /api/manufacturing/stock/v1/requests/{requestId} |
Em um momento posterior, foi liberada a operação de cancelamento da requisição, utilizando a
Observe que a versão da API, neste caso, está relacionada a transação CancelRequest e não a transação Request, já que o corpo da requisição não faz uso do modelo de mensagem de Request. Este exemplo também está mais alinhado com a orientação do guia de APIs, para vincular ações como recurso de uma entidade.Modelar a operação como um endpoint distinto da entidade que será atualizada:transação CancelRequest, versão 1.000,
teríamos o seguinte exemplo de APIconforme abaixo:
Bloco de código | |
---|---|
|
{
"Code": "10",
"RequestInternalId": "100",
"Type": "000",
"CancelDateTime": "2018-07-20T12:00:34-03:00",
"CancelReason": "Rejeitado",
"CancelRelatedRequests": ""
}
| |||
POST /api/manufacturing/stock/v1/requests/{requestId}/cancelRequest |
A API teve mais um endpoint adicionado, porém os demais endpoints não tiveram alteração de contrato. Neste caso, a versão do novo endpoint permanece igual a dos demais.
Consideremos agora, que a API teve alteração na versão da transação Request, evoluindo para a versão 1.001. Neste caso, devemos alterar a versão da API, modificando o minorVersion, conforme segue:
Bloco de código | ||||
---|---|---|---|---|
| ||||
GET /api/manufacturing/stock/v1.1/requests
GET /api/manufacturing/stock/v1.1/requests/{requestId}
POST /api/manufacturing/stock/v1.1/requests
PUT /api/manufacturing/stock/v1.1/requests/{requestId}
DELETE /api/manufacturing/stock/v1.1/requests/{requestId} |
Agora, teremos disponíveis os seguintes endpoints (observe que o endpoint de cancelamento permaneceu na v1):
Bloco de código | ||||
---|---|---|---|---|
| ||||
- V1
GET /api/manufacturing/stock/v1/requests
GET /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests
PUT /api/manufacturing/stock/v1/requests/{requestId}
DELETE /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests/{requestId}/cancelRequest
- V1.1
GET /api/manufacturing/stock/v1.1/requests
GET /api/manufacturing/stock/v1.1/requests/{requestId}
POST /api/manufacturing/stock/v1.1/requests
PUT /api/manufacturing/stock/v1.1/requests/{requestId}
DELETE /api/manufacturing/stock/v1.1/requests/{requestId} |
Seguindo com o nosso exemplo, vamos considerar agora, que o endpoint de cancelamento precisa, obrigatoriamente, receber um parâmetro de query. Isso altera consideravelmente o comportamento do endpoint. Sendo assim, o incremento deve ocorrer na majorVersion do endpoint, conforme segue.
Bloco de código | ||||
---|---|---|---|---|
| ||||
POST /api/manufacturing/stock/v2/request/{requestId}/cancelRequest?mandatoryParam=someValue |
Com isso, teremos o seguinte cenário de APIs:
Bloco de código | ||||
---|---|---|---|---|
| ||||
- v1
GET /api/manufacturing/stock/v1/requests
GET /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests
PUT /api/manufacturing/stock/v1/requests/{requestId}
DELETE /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests/{requestId}/cancelRequest
- v1.1
GET /api/manufacturing/stock/v1.1/requests
GET /api/manufacturing/stock/v1.1/requests/{requestId}
POST /api/manufacturing/stock/v1.1/requests
PUT /api/manufacturing/stock/v1.1/requests/{requestId}
DELETE /api/manufacturing/stock/v1.1/requests/{requestId}
- v2
POST /api/manufacturing/stock/v2/request/{requestId}/cancelRequest?mandatoryParam=someValue |
Depois de um certo tempo, pode ser necessário reorganizar o cenário das APIs disponíveis, concentrando em uma única versão todas as alterações realizadas. Neste caso, aproveitando a evolução da versão da transação cancelRequest para 2.000 (que afetaria apenas um endpoint), podemos ter uma nova majorVersion abrangendo os demais endpoints da API:
Bloco de código | ||||
---|---|---|---|---|
| ||||
- v1
GET /api/manufacturing/stock/v1/requests
GET /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests
PUT /api/manufacturing/stock/v1/requests/{requestId}
DELETE /api/manufacturing/stock/v1/requests/{requestId}
POST /api/manufacturing/stock/v1/requests/{requestId}/cancelRequest
- v1.1
GET /api/manufacturing/stock/v1.1/requests
GET /api/manufacturing/stock/v1.1/requests/{requestId}
POST /api/manufacturing/stock/v1.1/requests
PUT /api/manufacturing/stock/v1.1/requests/{requestId}
DELETE /api/manufacturing/stock/v1.1/requests/{requestId}
- v2
POST /api/manufacturing/stock/v2/request/{requestId}/cancelRequest?mandatoryParam=someValue
- v3
GET /api/manufacturing/stock/v3/requests
GET /api/manufacturing/stock/v3/requests/{requestId}
POST /api/manufacturing/stock/v3/requests
PUT /api/manufacturing/stock/v3/requests/{requestId}
DELETE /api/manufacturing/stock/v3/requests/{requestId}
POST /api/manufacturing/stock/v3/request/{requestId}/cancelRequest?mandatoryParam=someValue |
Portal TOTVS para APIs e Mensagem Padronizada
Guia de implementação de APIs TOTVS
Elaboração de mensagem padronizada REST/JSON
Elaboração de mensagem padronizada SOAP/XML
Repositório de mensagens padronizadas (TFS) - STABLE - requer login TOTVS para acesso.
Repositório de mensagens padronizadas (TFS) - DEV - requer login TOTVS para acesso.
Mapa de clientes internos TOTVS - lista os Product Owners e demais papeis que devem ser envolvidos no processo de aprovação.
Editor Swagger/OpenAPI - permite a criação de documentos Swagger 2.0 ou OpenAPI 3.0 através da edição de tags YAML.
Conversor Swagger 2.0 para OpenAPI - converte um documento Swagger 2.0 em um documento OpenAPI 3.0.
Modelador de APIs Restlet Studio - permite a modelagem de APIs (endpoints, tipos de dados, etc.) gerando documentação em Swagger 2.0 e RAML 1.0.
http://docs.oasis-open.org/ubl/prd1-UBL-2.1/UBL-2.1.html
http://docs.oasis-open.org/ubl/UBL-2.1-JSON/v1.0/cnd02/UBL-2.1-JSON-v1.0-cnd02.html
http://docs.oasis-open.org/ubl/UBL-2.1-JSON/v1.0/cnd02/json-schema/maindoc/
http://www.unece.org/cefact/brs/brs_index.html
Painel | ||
---|---|---|
| ||
|
Painel | ||
---|---|---|
| ||
|