Design da API: abordagem concreta versus abstrata - melhores práticas?

25

Ao discutir APIs entre sistemas (no nível de negócios), geralmente há dois pontos de vista diferentes em nossa equipe: algumas pessoas preferem uma abordagem abstrata mais - digamos - genérica , outras uma abordagem "concreta" direta.

Exemplo: o design de uma API simples de "pesquisa por pessoa". a versão concreta seria

 searchPerson(String name, boolean soundEx,
              String firstName, boolean soundEx,
              String dateOfBirth)

As pessoas a favor da versão concreta dizem:

  • a API é auto-documentada
  • É fácil de entender
  • é fácil validar (compilador ou como um serviço da web: validação de esquema)
  • BEIJO

O outro grupo de pessoas em nossa equipe diria "Essa é apenas uma lista de critérios de pesquisa"

searchPerson(List<SearchCriteria> criteria)

com

SearchCritera {
  String parameter,
  String value,
  Map<String, String> options
}

possivelmente criando "parâmetro" de algum tipo de enumeração.

Os proponentes dizem:

  • sem alterar a (declaração da) API, a implementação pode mudar, por exemplo, adicionando mais critérios ou mais opções. Mesmo sem sincronizar essa alteração no momento da implantação.
  • documentação é necessária mesmo com a variante de concreto
  • a validação do esquema é superestimada, geralmente é necessário validar mais, o esquema não pode lidar com todos os casos
  • já temos uma API semelhante com outro sistema - reutilizar

O contra-argumento é

  • muita documentação sobre parâmetros válidos e combinações válidas de parâmetros
  • mais esforço de comunicação porque é mais difícil de entender para outras equipes

Existem práticas recomendadas? Literatura?

erik
fonte
3
repetido "String first / name, boolean soundEx" é uma violação clara do dry e sugere que esse design falhou ao abordar o fato de que o nome deve corresponder ao soundEx. Diante de erros simples de design como esse, parece difícil prosseguir com uma análise mais sofisticada #
30613
O oposto de "concreto" não é "genérico", é "abstrato". A abstração é muito importante para uma biblioteca ou API, e essa discussão está falhando em fazer a pergunta verdadeiramente fundamental, fixando-se no que é francamente uma questão de estilo bastante trivial. FWIW, os contra-argumentos da opção B soam como uma carga de FUD; você não precisa de documentação ou comunicação extra se o design da API estiver meio limpo e seguir os princípios do SOLID.
Aaronaught 12/09
@Aaronaught obrigado por apontar isso ("resumo"). Pode ser um problema de tradução, "generisch" em alemão ainda parece bom para mim. Qual é a "questão verdadeiramente fundamental" para você?
Erik
4
@Aaronaught: A questão não é abstrata. A correção correta seria que o oposto de "genérico" é "específico", não "concreto".
Jan Hudec 12/09
Outro voto para não se tratar de genérico versus abstrato, mas genérico versus específico. O exemplo "concreto" acima é realmente específico para o nome, firstName e dateOfBirth, o outro exemplo é genérico para qualquer parâmetro. Nem são particularmente abstratos. Eu editar o título, mas eu não quero começar uma guerra de edição :-)
Matt freake

Respostas:

18

Depende de quantos campos você está falando e como eles são usados. O concreto é preferível para consultas altamente estruturadas com apenas alguns campos, mas se a consulta tende a ser muito livre, a abordagem concreta rapidamente se torna difícil de manejar com mais de três ou quatro campos.

Por outro lado, é muito difícil manter uma API genérica pura. Se você fizer uma pesquisa simples de nomes em muitos lugares, eventualmente alguém se cansará de repetir as mesmas cinco linhas de código e envolverá em uma função. Essa API invariavelmente evolui para um híbrido de uma consulta genérica junto com invólucros de concreto para as consultas mais usadas. E não vejo nada de errado nisso. Dá-lhe o melhor dos dois mundos.

Karl Bielefeldt
fonte
7

Projetar uma boa API é uma arte. A boa API é apreciada mesmo após o tempo passar. Na minha opinião, não deve haver viés geral na linha abstrato-concreto. Alguns parâmetros podem ser tão concretos quanto os dias da semana, alguns precisam ser projetados para serem extensíveis (e é bastante estúpido torná-los concretos, por exemplo, parte dos nomes das funções), mas outros podem ir ainda mais longe e ter uma aparência elegante. A API precisa fornecer retornos de chamada ou mesmo linguagem específica do domínio ajudará a combater a complexidade.

Raramente acontecem coisas novas sob a lua. Dê uma olhada na arte anterior, especialmente padrões e formatos estabelecidos (por exemplo, muitas coisas podem ser modeladas após feeds, descrições de eventos foram elaboradas em ical / vcal). Torne sua API facilmente aditiva, onde entidades frequentes e onipresentes são concretas e extensões previstas são dicionários. Existem também alguns padrões bem estabelecidos para lidar com situações específicas. Por exemplo, o tratamento de solicitações HTTP (e similares) pode ser modelado na API com objetos de Solicitação e Resposta.

Antes de projetar a API, faça um brainstorming sobre aspectos, incluindo aqueles que não serão incluídos, mas você deve estar ciente. Exemplos disso são idioma, direção da escrita, codificação, localidade, informações de fuso horário e similares. Preste atenção nos locais onde múltiplos podem aparecer: use list, e não um valor único para eles. Por exemplo, se você deseja a API para o sistema de videochat, sua API será muito mais útil, se você assumir N participantes, não apenas dois (mesmo que suas especificações no momento sejam essas).

Às vezes, ser abstrato ajuda a reduzir drasticamente a complexidade: mesmo se você projetar uma calculadora para adicionar apenas 3 + 4, 2 + 2 e 7 + 6, pode ser muito mais simples implementar X + Y (com limites tecnicamente viáveis ​​em X e Y e inclua ADD (X, Y) na sua API em vez de ADD_3_4 (), ADD_2_2 (), ...

Em suma, escolher um caminho ou outro é apenas um detalhe técnico. Sua documentação deve descrever casos de uso frequentes de maneira concreta.

Tudo o que você faz no lado da estrutura de dados, forneça um campo para uma versão da API.

Para resumir, a API deve minimizar a complexidade ao lidar com seu software. Para apreciar a API, o nível de complexidade exposta deve ser adequado. A decisão sobre a forma da API depende muito da estabilidade do domínio do problema. Portanto, deve haver uma estimativa de qual direção o software e sua API crescerão, pois essas informações podem afetar a equação da complexidade. Além disso, a criação da API está lá para as pessoas entenderem. Se houver boas tradições na área de tecnologia de software em que você está, tente não se desviar muito delas, pois isso ajudará a entender. Leve em conta para quem você escreve. Usuários mais avançados apreciarão a generalidade e a flexibilidade, enquanto aqueles com menos experiência podem se sentir mais à vontade com o concreto. No entanto, cuide da maioria dos usuários da API,

No lado da literatura, posso recomendar que os principais programadores do "Beautiful Code" expliquem como pensam Por Andy Oram, Greg Wilson, enquanto penso que a beleza é sobre perceber a otimização oculta (e a adequação para algum propósito).

Roman Susi
fonte
1

Minha preferência pessoal é ser abstrata, mas as políticas da minha empresa me impedem de ser concreto. Esse é o fim do debate para mim :)

Você fez um bom trabalho listando prós e contras de ambas as abordagens e, se continuar cavando, encontrará muitos argumentos a favor de ambos os lados. Desde que a arquitetura da sua API seja desenvolvida adequadamente - o que significa que você pensou em como ela será usada hoje e como ela poderá evoluir e crescer no futuro -, então você deve ficar bem.

Aqui estão dois indicadores que tive com pontos de vista opostos:

Favorecendo Classes Abstratas

Favorecendo Interfaces

Pergunte a si mesmo: "A API atende aos meus requisitos de negócios? Tenho critérios bem definidos para o sucesso? Ela pode ser dimensionada?". Essas parecem realmente boas práticas a serem seguidas, mas, honestamente, são muito mais importantes que concretas versus genéricas.

gws2
fonte
1

Eu não diria que uma API abstrata é necessariamente mais difícil de validar. Se os parâmetros do critério são simples o suficiente e têm poucas dependências entre si, não faz muita diferença se você passa os parâmetros separadamente ou em uma matriz. Você ainda precisa validar todos eles. Mas isso depende do design dos parâmetros dos critérios e dos próprios objetos.

Se a API for suficientemente complexa, ter métodos concretos não é uma opção. Em algum momento, você provavelmente terminará com métodos com muitos parâmetros ou métodos simples demais que não abrangerão todos os casos de uso necessários. Quanto à minha experiência pessoal no design de APIs consumidoras, é melhor ter métodos mais genéricos no nível da API e implementar wrappers necessários específicos no nível do aplicativo.

Pavels
fonte
1

O argumento de mudança deve ser descartado com YAGNI. Basicamente, a menos que você tenha pelo menos três casos de uso diferentes que usem a API genérica de maneira diferente, as chances são muito baixas de que você a projete para que não precise ser alterado no próximo caso de uso (e quando você tiver o casos, obviamente você precisa da interface genérica, ponto final). Portanto, não tente e esteja pronto para a mudança.

A alteração não precisa ser sincronizada para implantação nos dois casos. Quando você generaliza a interface posteriormente, sempre pode fornecer a interface mais específica para compatibilidade com versões anteriores. Mas, na prática, qualquer implantação terá tantas alterações que você a sincronizará de qualquer maneira, para não precisar testar os estados intermediários. Eu também não veria isso como argumento.

Quanto à documentação, qualquer solução pode ser fácil de usar e óbvia. Mas permanece como argumento importante. Implemente a interface para que seja fácil de usar nos seus casos reais. Às vezes, específico pode ser melhor e, às vezes, genérico.

Jan Hudec
fonte
1

Eu preferiria a abordagem da interface abstrata. Colocar uma consulta nesses tipos de serviço (de pesquisa) é um problema comum e provavelmente ocorrerá novamente. Além disso, você provavelmente encontrará mais candidatos a serviços adequados para reutilizar uma interface mais geral. Para poder fornecer uma interface comum coerente para esses serviços, não enumeraria os parâmetros de consulta atualmente identificados na definição da interface.

Como foi apontado anteriormente - eu gosto da oportunidade de alterar ou estender a implementação sem modificar a interface. A adição de outro critério de pesquisa não deve ser refletida na definição de serviço.

Embora não seja uma questão de projetar interfaces bem definidas, concisas e expressas, você sempre precisará fornecer alguma documentação além disso. Adicionar o escopo de definição para critérios de pesquisa válidos não é um fardo.

0x0me
fonte
1

O melhor resumo que eu já vi é a escala de Rusty, agora chamada manifesto de API Design da Rusty . Só posso recommentir fortemente esse. Por uma questão de exaustividade, cito o resumo da escala desde o primeiro link (quanto melhor no topo, pior a seguir):

Boas APIs

  • É impossível errar.
  • O compilador / vinculador não permitirá que você entenda errado.
  • O compilador avisará se você errar.
  • O uso óbvio é (provavelmente) o correto.
  • O nome diz como usá-lo.
  • Faça certo ou sempre quebrará em tempo de execução.
  • Siga a convenção comum e você acertará.
  • Leia a documentação e você acertará.
  • Leia a implementação e você acertará.
  • Leia a lista de discussão correta e você acertará.

APIs incorretas

  • Leia o tópico da lista de e-mails e você errará.
  • Leia a implementação e você entenderá errado.
  • Leia a documentação e você entenderá errado.
  • Siga a convenção comum e você errará.
  • Faça certo e, às vezes, será interrompido no tempo de execução.
  • O nome diz como não usá-lo.
  • O uso óbvio está errado.
  • O compilador avisará se você acertar.
  • O compilador / vinculador não permitirá que você acerte.
  • É impossível acertar.

As páginas de detalhes aqui e aqui vêm com uma discussão aprofundada de cada ponto. É realmente uma leitura obrigatória para designers de API. Obrigado Rusty, se você já leu isso.

JensG
fonte
0

Nas palavras dos leigos:

  • A abordagem abstrata tem a vantagem de permitir a construção de métodos concretos em torno dela.
  • O contrário não é verdade
Tulains Córdova
fonte
O UDP tem a vantagem de permitir que você construa seus próprios fluxos confiáveis. Então, por que quase todo mundo está usando TCP?
svick 12/09
Também há consideração da maioria dos casos de uso. Alguns casos podem ser necessários com tanta frequência, que é possível torná-los especiais.
Roman Susi
0

Se você estender a SearchCriteriaideia um pouco, ele pode lhe dar a flexibilidade como a criação AND, ORetc. critérios. Se você precisar dessa funcionalidade, essa seria a melhor abordagem.

Caso contrário, projete-o para usabilidade. Facilite a API para as pessoas que a usam. Se você tiver algumas funções básicas necessárias com freqüência (como procurar uma pessoa pelo nome), forneça-as diretamente. Se usuários avançados precisarem de pesquisas avançadas, eles ainda poderão usar o SearchCriteria.

Uooo
fonte
0

Qual é o código por trás da API? Se é algo flexível, uma API flexível é boa. Se o código por trás da API é muito específico, colocar uma cara flexível significa apenas que os usuários da API ficarão frustrados e irritados com tudo o que a API finge ser possível, mas na verdade não pode ser realizado.

Para o seu exemplo de pesquisa pessoal, todos os três campos são obrigatórios? Nesse caso, a lista de critérios é ruim porque permite uma infinidade de usos que simplesmente não funcionam. Caso contrário, exigir que o usuário especifique entradas não necessárias é ruim. Qual a probabilidade de a pesquisa por endereço ser adicionada na V2? A interface flexível facilita a adição do que a interface inflexível.

Nem todo sistema precisa ser ultra flexível, tentando fazer com que tudo seja o mesmo que o astronauta da arquitetura. Um arco flexível dispara flechas. Uma espada flexível é tão útil quanto uma galinha de borracha.

pedregoso
fonte