DEV Community

Apiumhub
Apiumhub

Posted on • Originally published at apiumhub.com on

iOS App Modularization with Apollo and SPM

Introduction

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
Enter fullscreen mode Exit fullscreen mode

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"
        )
    ]
)
Enter fullscreen mode Exit fullscreen mode

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" : {}
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

1. Schema Namespace:

"schemaNamespace" : "MyNamespaceGraphql"
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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 the GraphQLClient target with public access, these types can be readily utilized within your GraphQL client module.
  • Operations: json "operations" : { "inSchemaModule" : {} } By leaving inSchemaModule 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.

CTA Software

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Reusability : The Apollo Client module can be used across multiple projects, saving development time.
  2. Maintainability : Isolating the GraphQL code makes it easier to manage and update.
  3. Separation of Concerns : It keeps your main application codebase clean and focused.

Drawbacks of Modularization

  1. Initial Overhead : The setup process may seem like overkill for smaller projects.
  2. Dependency Management : Managing package dependencies can become complex.
  3. 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.

References

For further reading and detailed guidelines on Apollo GraphQL and Swift Package Manager, you may visit the following resources:

  1. Apollo GraphQL iOS Documentation:
  1. Swift Package Manager Documentation:

Feel free to delve into these documents to enhance your understanding and troubleshoot any challenges encountered during your development journey.

Top comments (0)