I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: não existe uma abordagem "melhor" ou "mais correta" para criar uma arquitetura de aplicativo. É muito trabalho criativo. Você deve sempre escolher a arquitetura mais direta e extensível, que ficará clara para qualquer desenvolvedor que começar a trabalhar no seu projeto ou para outros desenvolvedores da sua equipe, mas eu concordo que pode haver um "bom" e um "ruim" "arquitetura.
Você disse: collect the most interesting approaches from experienced iOS developers
não acho que minha abordagem seja a mais interessante ou correta, mas a usei em vários projetos e estou satisfeita com ela. É uma abordagem híbrida das que você mencionou acima e também com melhorias de meus próprios esforços de pesquisa. Sou interessante nos problemas de construção de abordagens, que combinam vários padrões e expressões conhecidas. Penso que muitos padrões empresariais da Fowler podem ser aplicados com sucesso aos aplicativos móveis. Aqui está uma lista dos mais interessantes, que podemos aplicar para criar uma arquitetura de aplicativo iOS ( na minha opinião ): Camada de serviço , Unidade de trabalho , Fachada remota , Objeto de transferência de dados ,Gateway , Supertipo de camada , ou criar sua própria camada leve de mapeamento / persistência de objetos leve, com base em SQLite bruto ou LevelDB, Caso Especial , Modelo de Domínio . Você sempre deve projetar corretamente uma camada de modelo e sempre não esquecer a persistência (isso pode aumentar significativamente o desempenho do seu aplicativo). Você pode usar Core Data
para isso. Mas você não deve esquecer que isso Core Data
não é um ORM ou um banco de dados, mas um gerenciador de gráficos de objetos com persistência como uma boa opção. Portanto, muitas vezes Core Data
pode ser muito pesado para suas necessidades e você pode procurar novas soluções, como Realm e Couchbase Lite. Também aconselho você a se familiarizar com o Domain Driven Design e o CQRS .
A princípio, acho que devemos criar outra camada para a rede, porque não queremos controladores de gordura ou modelos pesados e sobrecarregados. Eu não acredito nessas fat model, skinny controller
coisas. Mas acredito na skinny everything
abordagem, porque nenhuma classe deve ser gorda, nunca. Todas as redes geralmente podem ser abstraídas como lógica de negócios; consequentemente, devemos ter outra camada, onde podemos colocá-la. Camada de serviço é o que precisamos:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
Em nosso MVC
reino, Service Layer
há algo como um mediador entre o modelo de domínio e os controladores. Há uma variação bastante semelhante dessa abordagem chamada MVCS, onde a Store
é realmente a nossa Service
camada. Store
vends modela instâncias e lida com a rede, o cache etc. Quero mencionar que você não deve escrever toda a lógica de rede e de negócios em sua camada de serviço. Isso também pode ser considerado como um design ruim. Para mais informações, consulte os modelos de domínio Anêmico e Rico . Alguns métodos de serviço e lógica de negócios podem ser manipulados no modelo, por isso será um modelo "rico" (com comportamento).
Eu sempre uso extensivamente duas bibliotecas: AFNetworking 2.0 e ReactiveCocoa . Eu acho que é um item obrigatório para qualquer aplicativo moderno que interaja com a rede e os serviços da web ou contenha lógica complexa da interface do usuário.
ARQUITETURA
Inicialmente, crio uma APIClient
classe geral , que é uma subclasse de AFHTTPSessionManager . Esta é uma força de trabalho de todas as redes no aplicativo: todas as classes de serviço delegam solicitações REST reais a ele. Ele contém todas as personalizações do cliente HTTP, que eu preciso no aplicativo em particular: fixação de SSL, processamento de erros e criação de NSError
objetos diretos com motivos de falha detalhados e descrições de todos API
e erros de conexão (nesse caso, o controlador poderá mostrar mensagens corretas para usuário), configurando serializadores de solicitação e resposta, cabeçalhos http e outros itens relacionados à rede. Então eu logicamente dividir todas as solicitações de API em subserviços ou, mais corretamente, microservices : UserSerivces
, CommonServices
,SecurityServices
,FriendsServices
e assim por diante, de acordo com a lógica de negócios que eles implementam. Cada um desses microsserviços é uma classe separada. Eles juntos formam um Service Layer
. Essas classes contêm métodos para cada solicitação de API, modelos de domínio de processo e sempre retornam um RACSignal
com o modelo de resposta analisado ou NSError
para o responsável pela chamada.
Quero mencionar que se você tiver uma lógica de serialização de modelo complexa - crie outra camada para ela: algo como o Data Mapper, mas mais geral, por exemplo, JSON / XML -> Model mapper. Se você tiver cache: crie-o também como uma camada / serviço separado (você não deve misturar lógica de negócios com cache). Por quê? Porque a camada de armazenamento em cache correta pode ser bastante complexa com suas próprias dicas. As pessoas implementam lógica complexa para obter cache válido e previsível, como, por exemplo, cache monoidal com projeções baseadas em profunctors. Você pode ler sobre esta bela biblioteca chamada Carlos para entender mais. E não esqueça que o Core Data pode realmente ajudá-lo com todos os problemas de cache e permitirá que você escreva menos lógica. Além disso, se você tiver alguma lógica entre os NSManagedObjectContext
modelos de solicitação e servidor, poderá usar RepositórioPadrão de , que separa a lógica que recupera os dados e os mapeia para o modelo de entidade da lógica de negócios que atua no modelo. Portanto, aconselho usar o padrão de repositório mesmo quando você tiver uma arquitetura baseada em dados principais. Repositório pode coisas abstratas, como NSFetchRequest
, NSEntityDescription
, NSPredicate
e assim por diante para os métodos simples, como get
ou put
.
Depois de todas essas ações na camada Serviço, o responsável pela chamada (controlador de exibição) pode executar algumas tarefas assíncronas complexas com a resposta: manipulações de sinal, encadeamento, mapeamento etc. com a ajuda de ReactiveCocoa
primitivos, ou apenas se inscreva e mostre os resultados na exibição . Eu injetar com a injeção de dependência em todas estas classes de serviço meus APIClient
, que se traduzirá uma chamada de serviço especial em correspondentes GET
, POST
, PUT
, DELETE
, etc. pedido para o terminal REST. Nesse caso, APIClient
é passado implicitamente para todos os controladores, você pode explicitar isso explicando APIClient
as classes de serviço. Isso pode fazer sentido se você quiser usar personalizações diferentes doAPIClient
para classes de serviço específicas, mas se você, por algum motivo, não quiser cópias extras ou tiver certeza de que sempre usará uma instância específica (sem personalizações) do APIClient
- faça dele um singleton, mas NÃO, por favor, NÃO Faça aulas de serviço como singletons.
Em seguida, cada controlador de exibição novamente com o DI injeta a classe de serviço necessária, chama métodos de serviço apropriados e compõe seus resultados com a lógica da interface do usuário. Para injeção de dependência, eu gosto de usar o BloodMagic ou um framework Typhoon mais poderoso . Eu nunca uso singletons, APIManagerWhatever
aulas de Deus ou outras coisas erradas. Porque se você ligar para a sua turma WhateverManager
, isso indica que você não conhece seu objetivo e é uma má escolha de design . Singletons também é um anti-padrão e, na maioria dos casos (exceto os raros), é uma solução errada . Singleton deve ser considerado apenas se todos os três dos seguintes critérios forem atendidos:
- A propriedade da instância única não pode ser razoavelmente atribuída;
- Inicialização lenta é desejável;
- O acesso global não é previsto de outra forma.
No nosso caso, a propriedade da instância única não é um problema e também não precisamos de acesso global depois de dividirmos o nosso god manager em serviços, porque agora apenas um ou vários controladores dedicados precisam de um serviço específico (por exemplo, UserProfile
necessidades do controlador UserServices
e assim por diante) .
Devemos sempre respeitar o S
princípio no SOLID e usar a separação de preocupações . Portanto, não coloque todos os métodos de serviço e chamadas de rede em uma classe, porque é uma loucura, especialmente se você desenvolver um aplicativo corporativo de grande porte. É por isso que devemos considerar a injeção de dependência e a abordagem de serviços. Considero essa abordagem moderna e pós-OO . Nesse caso, dividimos nosso aplicativo em duas partes: lógica de controle (controladores e eventos) e parâmetros.
Um tipo de parâmetros seria parâmetros comuns de "dados". É o que repassamos funções, manipulamos, modificamos, persistimos, etc. Essas são entidades, agregados, coleções, classes de casos. O outro tipo seria parâmetros de "serviço". São classes que encapsulam a lógica de negócios, permitem a comunicação com sistemas externos, fornecem acesso a dados.
Aqui está um fluxo de trabalho geral da minha arquitetura, por exemplo. Vamos supor que temos um FriendsViewController
, que exibe a lista de amigos do usuário e temos a opção de remover dos amigos. Eu crio um método na minha FriendsServices
classe chamado:
- (RACSignal *)removeFriend:(Friend * const)friend
onde Friend
é um objeto de modelo / domínio (ou pode ser apenas um User
objeto se eles tiverem atributos semelhantes). Underhood este parses método Friend
para NSDictionary
de parâmetros JSON friend_id
, name
, surname
, friend_request_id
e assim por diante. Eu sempre uso a biblioteca Mantle para esse tipo de clichê e para a minha camada de modelo (analisando para frente e para trás, gerenciando hierarquias de objetos aninhadas em JSON e assim por diante). Depois de analisá-lo chama APIClient
DELETE
método para fazer um pedido de descanso efectivo e volta Response
em RACSignal
para o chamador ( FriendsViewController
no nosso caso) para exibir mensagem apropriada para o usuário ou qualquer outra coisa.
Se nossa aplicação é muito grande, temos que separar nossa lógica ainda mais claramente. Por exemplo, nem sempre é bom misturar Repository
ou modelar a lógica com Service
uma. Quando descrevi minha abordagem, eu disse que esse removeFriend
método deveria estar na Service
camada, mas se formos mais pedantes, podemos notar que ele pertence melhor Repository
. Vamos lembrar o que é Repositório. Eric Evans deu uma descrição precisa em seu livro [DDD]:
Um Repositório representa todos os objetos de um determinado tipo como um conjunto conceitual. Ele age como uma coleção, exceto com capacidade de consulta mais elaborada.
Portanto, a Repository
é essencialmente uma fachada que usa a semântica no estilo de coleção (Adicionar, Atualizar, Remover) para fornecer acesso aos dados / objetos. É por isso que quando você tem algo como: getFriendsList
, getUserGroups
, removeFriend
você pode colocá-lo no Repository
, porque coleção-like semântica é bastante clara aqui. E código como:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
é definitivamente uma lógica de negócios, porque está além das CRUD
operações básicas e conecta dois objetos de domínio ( Friend
e Request
), é por isso que deve ser colocada na Service
camada. Também quero observar: não crie abstrações desnecessárias . Use todas essas abordagens com sabedoria. Porque se você sobrecarregar seu aplicativo com abstrações, isso aumentará sua complexidade acidental, e a complexidade causará mais problemas em sistemas de software do que qualquer outra coisa
Descrevo um exemplo "antigo" do Objective-C, mas essa abordagem pode ser muito fácil de adaptar à linguagem Swift com muito mais melhorias, porque possui recursos mais úteis e açúcar funcional. Eu recomendo usar esta biblioteca: Moya . Permite criar uma APIClient
camada mais elegante (nosso cavalo de batalha, como você se lembra). Agora, nosso APIClient
provedor será um tipo de valor (enum) com extensões em conformidade com protocolos e alavancando a correspondência de padrões de desestruturação. Enumerações rápidas + correspondência de padrões nos permitem criar tipos de dados algébricos como na programação funcional clássica. Nossos microsserviços usarão esse APIClient
provedor aprimorado como na abordagem comum de Objective-C. Para a camada do modelo, em vez de Mantle
você pode usar biblioteca ObjectMapperou gosto de usar a biblioteca Argo mais elegante e funcional .
Então, descrevi minha abordagem arquitetônica geral, que pode ser adaptada para qualquer aplicativo, eu acho. Pode haver muito mais melhorias, é claro. Eu aconselho você a aprender programação funcional, porque você pode se beneficiar muito disso, mas também não vá muito longe. Eliminar um estado mutável global excessivo, compartilhado, criar um modelo de domínio imutável ou criar funções puras sem efeitos colaterais externos é, geralmente, uma boa prática, e uma nova Swift
linguagem incentiva isso. Mas lembre-se sempre de que sobrecarregar seu código com padrões funcionais puros pesados, abordagens teóricas de categoria é uma péssima idéia, porque outros desenvolvedores lerão e darão suporte ao seu código e poderão ser frustrados ou assustadores.prismatic profunctors
e esse tipo de coisa no seu modelo imutável. A mesma coisa com ReactiveCocoa
: não RACify
exagere muito no seu código , pois ele pode se tornar ilegível muito rápido, especialmente para iniciantes. Use-o quando realmente puder simplificar seus objetivos e lógica.
Então read a lot, mix, experiment, and try to pick up the best from different architectural approaches
,. É o melhor conselho que posso lhe dar.