Tailor 2

Why 2

It feels strange to be announcing a version 2 release only three months after the version 1 release. It wasn't the plan; we set out after WWDC to create a 1.1 release that would polish up rough edges and add support for new Swift features. As we started making use of Swift 2.0, particularly protocol extensions, we were more and more convinced that protocols are a better mechanism for sharing functionality and modeling types in the framework than class-based inheritence. One core goal of this framework is to be Swift-like, and it hardly seems right to let the framework lag behind the language. To that end, we're going to be pursuing a different versioning scheme that allows us to make the best use of the language while providing a manageable upgrade path.

Moving Fast

There will be two types of releases for Tailor: major versions and bug fixes. For major versions, we will do whatever possible to maintain compile-time compatibility with the previous major version without sacrificing features. We will only maintain compatibility going one version back: the methods that are deprecated in Tailor 2 will be removed in Tailor 3. Major versions will come out every three to six months for the foreseeable future; bug fixes will be released as often as needed, and will contain small, backward-compatible changes to address serious bugs.

When a new version of Tailor comes out, our recommendation is that you start work immediately on getting your apps to build with it. Ideally the app will still build and run, with deprecation warnings to indicate what needs to be changed for full compatibility. You'll need to update these things as soon as possible, because they'll be gone in the next release. This is definitely a harsh approach to upgrade cycles, but it's designed to follow Apple's lead.

Version 2 is a huge upgrade, and has more breaks in backward compatibility than we would like.

What's New in Version 2

You should check out the changelog for the full details on the new features; for this post we're going to focus on the changes that will require updates to your apps.

Protocols Everywhere

Many of the core types in the framework have been replaced with protocol extensions:

  • Template is now TemplateType
  • Controller is now ControllerType
  • Alteration is now AlterationScript
  • Task is now TaskType
  • Localization is now LocalizationSource
  • CacheStore is now CacheImplementation
  • DatabaseConnection is now DatabaseDriver
  • User is now UserType
  • Layout is now LayoutType

Many of these protocols require adding new fields to the types that implement them. You should read the guides for more information on how these protocols work. With these protocols in place, you should not have to subclass any class provided by the framework.

Request Handling

  • Sessions are now stored on requests, not controllers
  • Response codes are represented as a value from a ResponseCode enum, which provides both the code and a description
  • Instead of having a static method that returns a list of Actions, you should add your controller actions to the route set directly. There is a required static method on controllers called defineRoutes that allows you to add your routes. In your central route configuration, you should call addControllerRoutes to add the routes for a controller. That will call defineRoutes for you.
  • The best method for adding routes for controllers is the new route method on RouteSet. It takes in a path, an action method, and a name. You should read the controller guide in the docs for more details on how this works in practice.
  • Route paths are now represented by a RoutePath enum, which encapsulates both an HTTP method and a path. The enum cases correspond to the HTTP method, and all the cases take in a path as a parameter.

Modelling

  • The MySQL connection code has been factored out into the new TailorMysql framework.
  • The installer provides a new TailorSqlite framework, which provides a database driver backed by a SQLite data store.
  • The initializer required by the Persistable protocol now takes a DatabaseRow rather than a dictionary. It also now throws an exception on error rather than returning nil. The DatabaseRow type provides a read method that fetches values from the row, and throws an exception for missing values or for values that are not of the desired type. It infers the type from the value that you store the result in. You should read the model guide in the docs for more information on how to set up an initializer for a record.

Configuration

  • The ConfigurationSetting class has been replaced by the Application.Configuration type. This type provides a way of setting your configuration in a static, type-checked way rather than relying on plist files. There is a singleton instance of this type available at Application.configuration, and you should use that whenever fetching the configuration. For setting the configuration, you can add an extension to the Application.Configuration type that provides a dynamic implementation of the configure method. The configure method is called as part of the initialization of the configuration. You should read the configuration guide in the docs for more information on what configuration settings are available and what
  • Routes are now stored in a singleton instance available at RouteSet.shared. You can load your routes by calling RouteSet.load, and it is recommended that you do this in your custom configure method on the Configuration type.

Detailed Upgrade Process

You can find out more about the upgrade process in our upgrade guide.

Tailor 1.0

I've been quiet for a few months. I've been working hard in making Tailor more feature-complete and stable, in preparation for a 1.0 release. I'm pleased to say that the 1.0 release is now ready.  This release includes:

  • A revamped modeling system based around protocols that can support storing records in value types
  • More value types throughout the framework
  • Improved thread safety
  • A new date and time library with cleaner semantics and naming and easier APIs for time arithmatic and date formatting
  • A standalone framework with helpers for unit testing

I'm thrilled by this week's news that Swift is going to be open source and available on Linux. There are still plenty of pending details on how this will work, but I'm going to be making Tailor work on Linux as soon as the new compiler is available. I'm also excited about the new features in Swift 2, especially protocol extensions and error handling. Once I get back from WWDC, I'm going to start working on integrating these features into Tailor as part of version 1.1.

Going forward, we're going to be using semantic version, consisting of a major version, a minor version, and a bug fix version. In the long term our goal is for minor versions to focus on self-contained new features that maintain backward-compatibility, and for major versions to focus on significant refactoring a that may require breaking backward-compatibility. While the framework is still young and Swift is still evolving rapidly, this may be difficult. We're going to take an aggressive stance on adopting new Swift versions, following Apple's lead. New minor or major versions of the framework will be built on the latest Swift version that has been fully released.

It's clear this week that Swift has a bright future for more than just Mac and iOS apps, and I think that Tailor is going to be a key part of that future. I can't wait to see what's next.

- JBL

Fun with Swift's Type System

Working on Tailor over the last few months has given me great admiration for Swift's type system. Still, it's not always the easiest thing to work with when writing an ORM. I ran into a bizarre behavior when writing the query builder, and I wanted to write up my findings here.

Let's say you have a generic type that wants to initialize a record of its type parameter. There are some scenarios where it can initialize it as the wrong type. The bug crops up when:

  • You are using a required initializer
  • The type parameter for your generic type is constrained to have a given base class
  • The actual type parameter you've specified is a subclass of that base class

I've attached a playground that demonstrates the problem. At first I thought I was out of luck, but there is a very strange workaround. You have a class called GenericInitializer, which has a HatType parameter. If you try to use HatType.self.init, it will always initialize it as a Hat, even if HatType is defined to be TopHat. Right before the initializer is called, it acknowledges that HatType is TopHat, but it will spit out a plain old Hat. The type system will think it's a TopHat, but if you try to access TopHat's fields it will go haywire. If, on the other hand, you store HatType.self in a local variable called type, and then call type.init, it will correctly initialize it as a TopHat. I'm not sure how storing an expression in a local variable before calling it changes the semantics, but Swift's compiler is clever like that.

You can download the playground and try it for yourself.

Tailor

When I first saw Swift in June, I knew that I wanted to use it everywhere. Apple promoted it as a language that could span from operating systems to simple scripts. I wanted that future where I could build web apps as cousins to native apps, with the same tools and languages. I wanted that balance between performance, safety, and development speed. After a few months of playing around with Swift and following the chatter in the community, I was surprised that I didn't see anyone moving in that direction. I started to think that it was just a crazy, impractical idea.

To prove myself wrong, I started building Tailor.

After a couple months of work, Tailor has started to get interesting. It can accept network connections. It has a MySQL wrapper with a tiny ORM. It supports building routes around controllers, with a simple templating system. It has a built-in authentication library with secure password storage. 

I'm eager to get feedback from other people in the community with different backgrounds and more experience. I think this project is the beginning of something interesting, but it is still the beginning. I don't even have a production environment that can host this blog. I have an ample supply of ideas for where to take this over the next several months:

  • Expanding the libraries for localization, authentication, and modeling 
  • Adding libraries for mailers, background jobs, and serving APIs
  • Building a suite of demo apps that can be run in a production server
  • Improving the documentation and simplifying the process of getting started building Tailor apps

The code is available on Github, and I'm working on sample apps. You can reach out to me with any feedback. I'm excited to see what the community thinks of my quixotic endeavor. 

- Brownlee