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. Para saber das últimas modificações neste documento, consulte o histórico de alterações no final da página. |
Verifique se a entidade sobre a qual a API atuará possui uma transação já definida no repositório de mensagens padronizadas TOTVS.
No portal api.totvs.com.br, consulte o item Mensagem Padronizada, subitem Lista de Serviços.
No TFS, acesse a Project Collection TOTVSMSGXML (http://tfs2015.totvs.com.br:8080/tfs/TOTVSMSGXML).
Consulte o repositório de mensagens padronizadas em formato JSON. Primeiramente, a pasta STABLE/messages-json/jsonschema, depois a pasta DEV/messages-json/jsonschema.
Se não encontrar uma transação, consulte o repositório de mensagens em formato XML, verificando as pastas STABLE/messages-xml/xmlschema e DEV/messages-xml/xmlschema.
Selecione, sempre que possível, a maior versão da transação.
Existindo apenas no repositório XML uma transação compatível, deve-se criar o documento
Json Schema equivalente ao documento XSD.
O documento
Json Schema deve ser armazenado no TFS, na pasta DEV/messages-json/jsonschema.
O modelo de dados pode ser segmentado em blocos menores, desde que obedeçam a estrutura hierárquica e
a estrutura e campos seja equivalente ao documento XSD. Caso a hierarquia seja alterada ou seja retirado ou adicionado campos no modelo, isso implica em uma nova versão da transação. Exemplos de segmentação serão mostrados adiante neste documento.
Havendo necessidade de se criar uma nova versão da transação, esta pode ser criada apenas no repositório JSON, desde que se siga as orientações previstas na documentação de elaboração de mensagem padronizada SOAP/XML, realizando os devidos ajustes para o formato JSON.
Lembrando que as versões devem começar de 2.000 pois, por convenção, foi a partir desta que se iniciou o uso de InternalId.
Sempre que possível, utilizar padrões internacionais, conforme passo 3 do processo de elaboração de API e mensagens padronizadas.
Para saber se já existe uma mensagem de conta contábil, por exemplo, pesquise no Google usando "account xsd oasis repository".
Transações criadas para atender uma exigência legal devem se ater estritamente ao que é definido pela legislação. Nestes casos, o nome da transação e dos campos podem ser em português, se a legislação exigir.
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.0Json Schema, teríamos o seguinte documento (alguns elementos previstos nos guias de implementação de APIs e para documentação da mensagem padronizada - REST/JSONforam omitidos para melhor compreensão):
Bloco de código | ||||||
---|---|---|---|---|---|---|
| ||||||
{ "openapi$schema": "3.0.0https://raw.githubusercontent.com/totvs/ttalk-standard-message/master/jsonschema/schemas/events/contract_2_000.json#", (...), "components": { "schemas"definitions": { "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 Json Schema poderia ser segmentado em 3 submodelos:
Convertendo isso para o modelo OpenAPIJson Schema, 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$schema": "3.0.0", https://raw.githubusercontent.com/totvs/ttalk-standard-message/master/jsonschema/schemas/events/contract_2_000.json#", (...), "componentsdefinitions": { "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": { "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": { " "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" }, "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}/v1/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 pelo guia de APIs, sendo independente da versão da mensagem padronizada. Entretanto, mudanças no modelo de dados implicam 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:
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 transação CancelRequest, versão 1.000, conforme abaixo:
Bloco de código | ||||
---|---|---|---|---|
| ||||
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 |
Aviso | ||
---|---|---|
| ||
Após a reorganização da API pode ser necessário excluir a versão anterior e neste caso, a depreciação deve ser feita conforme politica de cada linha de produto. |
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
Histórico de Alterações |
---|
Painel | ||
---|---|---|
| ||
|