In the last article, we gave a general introduction to using Swift for backend development. We discussed its advantages: its robustness, stability and, above all, its simplicity.
Now we’re going to drill a little deeper, and provide some best practices that will help you in your day-to-day work. Our work today will focus on Vapor, an open-source web framework that provides a robust foundation for websites, APIs and Cloud projects. If you have any questions, just reach to us and we’ll do our best to help.
Table of Contents
First, let’s explore some best practices for testing backend services
There’s a lot of stuff we could discuss here, but here are four essential tips I personally find useful.
1) Automate tools for your testing process. Continuous Integration (CI) tools like GitHub Actions, GitLab CI and Jenkins can run on every commit or pull request, catching problems early and guaranteeing good code quality.
2) Aim for a good amount of tests, but understand that it’s not always feasible or necessary to reach 100% coverage. In fact, it’s best to focus only on the core critical paths and functionality that matters to your users. 3) Use performance testing to point out the bottlenecks or possible issues during scalability. I’d recommend using tools like Apache JMeter, which will simulate various loads on your application.
4) Look for possible vulnerabilities at all times. Static analysis or security-focused testing tools can fulfil this function in your CI/CD pipeline.
Ok, now let’s look at a unit testing example with XCTest
XCTest is the default unit testing framework for XCode, known for being both comprehensive and accessible. Here’s what a test with Vapor will look like.
// VATServices.swift
import Vapor
struct VATServices {
func calculateVATTax(amountV: Double, rateV: Double) -> Double {
return amountV * rateV / 100
}
}
// VATServicesTest.swift
import XCTest
@testable import App
final class VATServicesTest: XCTestCase {
func testCalculateVATTex() {
let vatServices = VATServices()
let result = vatServices.calculateVATTax(amountV: 200, rateV: 10)
XCTAssertEqual(result, 20, "This VAT calculation should be good")
}
}
Integration testing example with Vapor
We need to test the full process of receiving a request, processing it through our routes, and ensuring the response is as expected. This is where integration testing comes in.
Here’s an example of a test that checks a simple route and assures that it returns a 200 status code (meaning that the request was successful), with the message “Hello, world tested!”
The XCTVapor
package provides the utilities to test requests and responses. Here’s an example.
// ApplicationTest.swift
import XCTVapor
@testable import App
final class ApplicationTest: XCTestCase {
var app: Application!
override func setUp() {
super.setUp()
app = Application(.testing)
try! configure(app)
}
override func tearDown() {
super.tearDown()
app.shutdown()
}
func testTheHelloWorldRoute() throws {
try app.test(.GET, "helloWorld", afterResponse: { res in
XCTAssertEqual(res.status, .ok)
XCTAssertEqual(res.body.string, "Hello, world Tested!")
})
}
}
Debugging Swift backend code
Debugging with LLDB
Ok, now let’s go a little further.
When we run a Swift backend app, we typically face issues with breakpoints and inspecting variables. Using a Low Level Debugger, we have the ability to pause the execution and test the state of the function when the program is running.
- Set a breakpoint in your code in the Xcode or by using
breakpoint set
command in LLDB. - Run the application in debug mode. From the terminal, we can start LLDB with
lldb .build/debug/ExecutableName
. - When execution pauses at the breakpoint, we should use LLDB commands to inspect variables or step through the code.
We can use**print variableName
** to print the value of the variable, and**step
** or next
to move through the code line by line.
Logging for debugging
Logging into the application can help track down issues by providing runtime info. Here’s an example of a test.
import LoggingInfo
func route(_ app: Application) throws { app.get("helloRoute") { req -> String in
req.logger.info("Hello this route was accessed")
return "Hello, Bugfender world!"
}
}
Deploying & Scaling Swift Backend Services
Now, the fun part: we can implement continuous integration continuous deployment (CI/CD) pipelines, creating scalable architectural patterns in load balancing, microservices architecture, and serverless architecture. This is key to ensuring a strong, robust backend infrastructure.
Here are some popular deployment options:
Bare Metal
This gives you more control of the overall infrastructure, but it involves more effort in setup and installation. In fact, you must step up Swift and prepare your app to run on a dedicated Linux server.
The server configuration setup should be started on boot, and you should deploy reverse proxy settings with Nginx or Apache servers to handle HTTP requests. Docker
Containerisation with Docker will pack your app and its all dependencies into a container, assuring consistency from one environment to another. Here’s an example:
FROM swift:latest as builder
WORKDIR /app
COPY.
RUN swift build -c release
FROM swift:slim
WORKDIR /app
COPY --from=builder /app/.build/release /app
CMD ["./Bugfender"]
Cloud Services like AWS and Azure
Cloud technology provides the necessary managed services and infrastructure, allowing deployment and scaling of the application with less overhead.
Continuous Integration/Continuous Deployment (CI/CD) Pipelines for Swift
Now, let’s look at some CI/CD pipelines. Here’s a quick example:
GitHub Actions Sample code:
name: Swift CI/CD
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Swift
uses: fwal/setup-swift@v1
- name: Build
run: swift build
- name: Testing
run: swift test
- name: Dockerize and Deploy
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
docker build -t Bugfender .
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
docker push Bugfender
Conclusion
Now you should have all the knowledge you need to start using Swift for backend projects. As well as a general overview of the tools at your disposal (and the strengths and weaknesses of each one), you’ve seen plenty of code that should help you get up and running. Hopefully these articles have been accessible, engaging, and fun.
But if you require any further guidance, or have any specific questions, don’t hesitate to get in touch. We love to talk about this kind of stuff (or anything dev-related really), so let’s chat!