IOS App Modularization With Apollo And SPM
In the modern development landscape, GraphQL has revolutionized the way we think about APIs and data retrieval. When it comes to iOS development, integrating GraphQL can be a breeze, thanks to libraries like Apollo. But how do you take it a step further and make your implementation modular using Swift Package Manager (SPM)? This article will guide you through the benefits, drawbacks, and step-by-step process of achieving this modular approach.
Why Apollo?
Apollo has become one of the most popular GraphQL clients owing to its robust set of features, including intelligent caching, real-time updates, and a strong type system. But one of its less talked-about advantages is its compatibility with Swift and native support for modularization through SPM.
The Need for Modularization
As applications grow, maintaining clean, reusable code becomes challenging. Modularization, or the process of dividing a program into separate sub-programs, is a strategy to manage this complexity. By creating a separate module for the Apollo GraphQL client, we can:
- Keep GraphQL-related code isolated from other parts of the app.
- Reuse the module across multiple projects.
- Make the codebase easier to manage and understand.
How To Implement a Modular Apollo GraphQL Client
Step 1: Initialize a New Swift Package
In your project directory, run:
swift package init --type library GraphQLClient
This command initializes a new Swift package named GraphQLClient
.
Step 2: Configuring the Swift Package
Now, let’s create a robust Package.swift
that defines our module along with a separate testing target and a plugin for the Apollo CLI.
// swift-tools-version: 5.7
import PackageDescription
let package = Package(
name: "GraphQLClient",
platforms: [
.iOS(.v14),
.macOS(.v10_14)
],
products: [
.library(
name: "GraphQLClient",
targets: ["GraphQLClient"]),
.library(name: "GraphQLClientTesting",
targets: ["GraphQLClientTesting"]),
.plugin(name: "GenerateApolloCli",
targets: ["GenerateApolloCli"])
],
dependencies: [
.package(url: "https://github.com/apollographql/apollo-ios.git",
from: "1.3.3")
],
targets: [
.target(
name: "GraphQLClient",
dependencies: [.product(name: "Apollo", package: "apollo-ios")],
path: "./Sources",
exclude: ["Tests"],
swiftSettings: [
.unsafeFlags(["-suppress-warnings"])
]),
.target(name: "GraphQLClientTesting",
dependencies: [.product(name: "ApolloTestSupport", package: "apollo-ios")],
path: "./Sources/Tests"),
.plugin(
name: "GenerateApolloCli",
capability: .command(
intent: .custom(
verb: "apollo-cli-generate", // Verb used from the command line
description: "Generates graphql"),
permissions: [
.writeToPackageDirectory(reason: "Generate code for graphql")
]),
dependencies: [
.product(name: "apollo-ios-cli", package: "apollo-ios")
],
path: "Plugins/GenerateApolloCli"
)
]
)
Within this configuration, we’re defining three significant components:
1. GraphQL Client: GraphQLClient
This serves as our Apollo client, the core engine through which we’ll send GraphQL queries and mutations. By modularizing it, we ensure a clean separation from our application logic, allowing for easy updates and potential reuse across projects.
This target includes dependencies for Apollo and sets the path and exclusions for our source files, ensuring clean navigation and minimal build warnings.
2. Testing Module: GraphQLClientTesting
Aiming for solid testing practices, we separate our testing concerns by establishing a dedicated testing module.
This enables the usage of mock responses, creating a controlled environment for our integration tests and ensuring our app’s logic handles data correctly without making actual API calls.
3. Code Generation Plugin: GenerateApolloCli
Code generation is a pivotal feature in GraphQL development, automating the creation of query structures and types. With Apollo CLI’s code generation and our custom Xcode plugin, we enhance our development workflow.
This plugin allows developers to execute Apollo CLI code generation directly from Xcode, simplifying the process and enhancing productivity by reducing context-switching between the terminal and IDE.
Step 3: Apollo Codegen Configuration
When working with Apollo in Swift, the apollo-codegen-config.json
file plays a pivotal role in steering code generation. It configures the Apollo CLI’s operations when it’s generating types and operations for your GraphQL queries. Let’s dissect a sample configuration:
{
"schemaNamespace" : "MyNamespaceGraphql",
"input" : {
"operationSearchPaths" : ["**/*.graphql"],
"schemaSearchPaths" : ["**/*.graphqls"]
},
"output" : {
"testMocks" : {
"absolute" : {
"path": "./Sources/Tests/Mocks",
"accessModifier": "public"
}
},
"schemaTypes" : {
"path" : "./Sources/GraphQLClient/Generated",
"moduleType" : {
"embeddedInTarget": {
"name": "GraphQLClient",
"accessModifier": "public"
}
}
},
"operations" : {
"inSchemaModule" : {}
}
}
}
1. Schema Namespace
"schemaNamespace" : "MyNamespaceGraphql"
This defines the namespace for the generated schema types, ensuring that your GraphQL types are encapsulated under a dedicated namespace, MyNamespaceGraphql
, preventing naming conflicts and ensuring clean integration within your Swift code.
2. Input Configuration
"input" : {
"operationSearchPaths" : ["**/*.graphql"],
"schemaSearchPaths" : ["**/*.graphqls"]
}
The input
section dictates where Apollo should search for .graphql
and .graphqls
files within your project, allowing you to organize your GraphQL documents flexibly without restricting them to a single directory.
3. Output Configuration
The output
section is more granular, controlling the destinations and access levels of generated code and mocks.
- Test Mocks
"testMocks" : { "absolute" : { "path": "./Sources/Tests/Mocks", "accessModifier": "public" } }
This subsection ensures that your generated test mocks (mocked data responses for your operations) reside in./Sources/Tests/Mocks
and are publicly accessible, facilitating simplified testing. - Schema Types
"schemaTypes" : { "path" : "./Sources/GraphQLClient/Generated", "moduleType" : { "embeddedInTarget": { "name": "GraphQLClient", "accessModifier": "public" } } }
Here, we guide the Apollo CLI to place generated schema types in./Sources/GraphQLClient/Generated
. Furthermore, by embedding them in theGraphQLClient
target with public access, these types can be readily utilized within your GraphQL client module. - Operations
json "operations" : { "inSchemaModule" : {} }
By leavinginSchemaModule
empty, we’re instructing Apollo to generate operation types (query, mutation, and subscription handling types) in the same module as the schema types, ensuring cohesion and ease of access in your Swift code.
Step 4: Incorporating the Schema Definition Language (SDL) for Code Generation
The essence of interacting with a GraphQL API pivots on understanding the API’s schema—its types, queries, mutations, and subscriptions. The Schema Definition Language (SDL) is foundational in this, providing a structural and type definition of the API that Apollo utilizes to generate corresponding Swift code.
Why Is SDL Crucial?
The SDL provides a blueprint of the GraphQL API, describing all possible queries, mutations, and data structures in your API. Without it, Apollo’s codegen tool would lack the necessary context for generating types and operations that align with the API.
Embedding SDL in Your Project
To involve SDL in code generation, ensure the .graphqls
file containing the SDL of your GraphQL API is placed in the path specified in your apollo-codegen-config.json
.
Step 5: Defining Queries and Mutations With .graphql
Files
Crafting and managing your queries and mutations is a quintessential step in shaping your GraphQL interactions and, consequently, the generated code via Apollo. Leveraging .graphql
files allows you to articulate the exact operations your app will perform, ensuring Apollo generates only the requisite code.
Formulating .graphql
Files
1. Define Precisely
Each .graphql
file should encapsulate a single query, mutation, or subscription. This ensures clarity and makes tracking changes in version control systems like git more straightforward.
2. Organize Strategically
Store .graphql
files in a logical, hierarchical directory structure that reflects their usage within your app. For instance, grouping all user-related operations within a /user
directory.
# Example Query in a .graphql File
query GetUser($userID: ID!) {
user(id: $userID) {
id
name
email
}
}
Tailoring Code Generation
By specifying the exact operations your app will utilize, Apollo CLI will generate Swift code that is:
- Minimized: Only necessary types and operations are generated.
- Optimized: Ensures your app is not burdened with unused code and types, streamlining your binary and minimizing potential points of failure.
Ensure that your .graphql
files are stored in the directory specified in your apollo-codegen-config.json
, enabling Apollo CLI to locate and utilize them during code generation.
"input" : {
"operationSearchPaths" : ["**/*.graphql"]
}
With your queries and mutations strategically defined and organized, you not only streamline your code generation but also enhance the clarity and maintainability of your operations. The lean, tailored code generated by Apollo ensures your app remains optimized and robust, regardless of the complexity of your GraphQL API.
Your steps, from SDL incorporation to query and mutation definition, provide a seamless and efficient approach to leveraging GraphQL with Apollo in Swift, ensuring your development is not just robust and type-safe but also a pleasurable, coherent experience.
Advantages of Modularization
- Reusability: The Apollo Client module can be used across multiple projects, saving development time.
- Maintainability: Isolating the GraphQL code makes it easier to manage and update.
- Separation of Concerns: It keeps your main application codebase clean and focused.
Drawbacks of Modularization
- Initial Overhead: The setup process may seem like overkill for smaller projects.
- Dependency Management: Managing package dependencies can become complex.
- Versioning: Keeping the module in sync with the main project requires a versioning strategy.
Conclusion
Implementing a modular Apollo GraphQL client via Swift Package Manager not only makes your codebase cleaner but also enhances reusability and maintainability. While there may be some initial setup overhead and additional complexities in dependency management, the long-term benefits often outweigh these drawbacks. By leveraging both Apollo and SPM, you can create robust, modular, and efficient iOS applications.