Versões comparadas

Chave

  • Esta linha foi adicionada.
  • Esta linha foi removida.
  • A formatação mudou.

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.

Orientações gerais

  1. 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).
    1. Consulte o repositório de mensagens padronizadas em formato JSON. Primeiramente, a pasta STABLE/messages-json/jsonschema, depois a pasta DEV/messages-json/jsonschema.
    2. 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.
    3. Selecione, sempre que possível, a maior versão da transação.
  2. Existindo apenas no repositório XML uma transação compatível, deve-se criar o documento OpenAPI (antigo Swagger) equivalente ao documento XSD.
    1. O documento OpenAPI deve ser armazenado no TFS, na pasta DEV/messages-json/jsonschema.
    2. O modelo de dados pode ser segmentado em blocos menores, desde que obedeçam a estrutura hierárquica e tenham apenas os atributos previstos no 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.
  3. 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.
    1. 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.
    2. 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".
    3. 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.
  4. Modele o documento da API usando o padrão OpenAPI 3.0, seguindo as orientações para elaboração de mensagem padronizada REST/JSON.
    1. As tags x-totvs devem ser usadas para documentar a mensagem e os atributos do modelo de dados.
    2. Em relação ao internalId e ListOfInternalId (retorno), seguir as orientações do item modelagem de transações.
    3. O uso de OData é opcional, sendo indicado para os casos onde a filtragem de registros necessite de mais recursos.
    4. Em relação ao versionamento, veja o item correspondente, neste documento.
    5. O nome da transação deve estar no plural, para manter consistência com o guia de implementação de APIs (ver item Estrutura de URLs).
      Modelo: http://{servidor}:{porta}/api/{produto}/{agrupador}/{versao_transacao}/{nome_transacao}.
      Exemplo: http://api.totvs.com.br:8080/api/datasul/financeiro/v2.004/accountantAccounts.
  5. Submeta o documento OpenAPI para análise e aprovação.
    1. Pelos POs de segmento, conforme passo 5 do processo de elaboração de API e mensagens padronizadas.
      1. Criar um tópico no Ryver (forum API) com o nome da mensagem a ser aprovada.
    2. Pelo comitê de integração, conforme orientações do passo 6 do processo.
  6. Após aprovação da mensagem pelo comitê, o responsável pela modelagem deve mover o documento, no TFS, para a pasta STABLE/messages-json/jsonschema, bem como publicar a API no portal api.totvs.com.br.

Segmentação do modelo de dados

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
languagejs
titleContract 2.000 - OpenAPI - monolítico
collapsetrue
{
  "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:

  • ContractModel, correspondente ao cabeçalho do contrato.
  • SheetModel, correspondente às folhas do contrato.
  • ItemModel, correspondente aos itens vinculados às folhas do contrato.

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
languagejs
titleContract 2.000 - OpenAPI - segmentado
collapsetrue
{
  "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
languagetext
/api/{produto}/{agrupador}/v2.000/contracts/{ContractUniqueId}/sheets/{SheetNumber}/items/{ItemCode}

Neste template, temos as seguintes considerações:

  • O item ContractUniqueId deve ser um identificador único, já que a entidade Contract prevê uma chave composta. A forma como esta chave será representada ficará a cargo da área de negócio definir. Neste exemplo específico, poderiamos assumir a concatenação dos atributos ContractNumber e ContractReview, separados por "|". Ex: 1|1.
  • No nível de contracts utilizamos o submodelo ContractModel. Nos atributos ListOfSheet e ListOfItem, utilizamos os submodelos SheetModel e ItemModel quando os mesmos forem expandidos.
  • No nível de sheets, utilizamos apenas o submodelo SheetModel. O modelo ItemModel será utilizado quando o atributo ListOfItem for expandido.
  • No nível de items, utilizamos apenas o submodelo ItemModel.
  • Observe a equivalencia entre o item do modelo e o item de path correspondente:
    • ListOfSheet (atributo de ContractModel) equivale a sheets.
    • ListOfItem (atributo de SheetModel) equivale a items.

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
languagejs
titlePOST /v2.000/contracts/
collapsetrue
{
  "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
languagejs
titlePOST /v2.000/contracts/
collapsetrue
{
  "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
languagejs
titleGET /v2.000/contracts
collapsetrue
{
  "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
languagejs
titleGET /v2.000/contracts/1|1?expand=ListOfSheet.ListOfItem
collapsetrue
{
  "_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
languagejs
titlePOST /v2.000/contracts/1|1/sheets
collapsetrue
{
  "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
languagejs
titlePOST /v2.000/contracts/1|1/sheets/1/items
collapsetrue
{
  "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

  • DELETE /v2.000/contracts/1|1/sheets/1/items/1

Eliminando uma folha de um contrato

  • DELETE /v2.000/contracts/1|1/sheets/1

Versionamento da API

O versionamento de APIs que atuam sobre entidades com mensagem padronizada seguirá o padrão definido para mensagem padronizada. Teremos evolução da versão da API sempre que houver mudança 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.000.

Exemplos de APIs versionadas:

  • /api/datasul/financeiro/v2.000/accountantAccounts
  • /api/rm/educacional/v1.000/disciplines
  • /api/protheus/rh/v3.002/employees

No caso de APIs relacionadas a transações do tipo Request, considerar o exposto no tópico a seguir.

Modelagem de APIs para mensagens do tipo Request

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:

  1. 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
    languagejs
    titlePOST /api/datasul/financeiro/v1.000/requests/100/cancelRequest
    collapsetrue
    {
      "Code": "10",
      "RequestInternalId": "100",
      "Type": "000",
      "CancelDateTime": "2018-07-20T12:00:34-03:00",
      "CancelReason": "Rejeitado",
      "CancelRelatedRequests": ""
    }


    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.

  2. Modelar a operação como um endpoint distinto da entidade que será atualizada:
    Considerando a mesma transação CancelRequest, versão 1.000, teríamos o seguinte exemplo de API:

    Bloco de código
    languagejs
    titlePOST /api/datasul/financeiro/v1.000/cancelRequest
    collapsetrue
    {
      "Code": "10",
      "RequestInternalId": "100",
      "Type": "000",
      "CancelDateTime": "2018-07-20T12:00:34-03:00",
      "CancelReason": "Rejeitado",
      "CancelRelatedRequests": ""
    }


    Nesta forma, o recurso é a própria operação, sendo que a entidade que será alterada está referenciada no corpo da requisição. Este modelo deixa mais claro o vínculo entre versão da API e modelo de mensagem padronizada.

Links relacionados

TOTVS - Mensagem padronizada e APIs

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.

TOTVS - diversos

Mapa de clientes internos TOTVS - lista os Product Owners e demais papeis que devem ser envolvidos no processo de aprovação.

OpenAPI - especificação e ferramentas

Especificação OpenAPI 3.0

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.

Padrões internacionais de mensagens para integração

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.hl7.org/

http://www.unece.org/cefact/brs/brs_index.html


Painel
titleIndice

Índice