├── manuscript ├── part-appendix │ ├── 300-bonus.txt │ ├── 400-unowned self in closures.txt │ ├── 100-links.txt │ ├── 500-objc api.txt │ ├── 200-books.txt │ ├── 700-adopting swift 2.txt │ └── 600-on swift.txt ├── part0-intro.txt ├── part-content.txt ├── images │ ├── title_page.jpg │ ├── 201503130948_viper.jpg │ ├── 201507230959_tree.png │ ├── 201507231210_mvvm.png │ ├── 201507281218_trees.png │ ├── 201501051800_window.png │ ├── 20141206190120_coredata.png │ ├── 20141125103807_components.jpg │ ├── 201507231001_window-strip.png │ ├── 201507231024_layered-hex.png │ ├── 20150723132836_viper-arch.png │ ├── 20150723113926_layers-ports.png │ ├── 20141204100034_ports-adapters.png │ ├── 20141117095632_target-membership.png │ └── 20141125103816_components--event_handler.jpg ├── part-appendix.txt ├── 201412051558 multiple consumers.txt ├── sample-stripped.txt ├── newsletter.txt ├── Sample.txt ├── README.md ├── part3-domain.txt ├── part4-coredata.txt ├── Book.txt ├── part0-intro │ ├── 400-the example.txt │ ├── 200-contribute.txt │ ├── 100-who.txt │ ├── 300-motivation.txt │ ├── 500-architecture.txt │ └── 600-patterns.txt ├── part9-epilogue │ └── 100-assessment.txt ├── part1-bootstrapping.txt ├── part2-process.txt ├── part4-coredata │ ├── 100-changes for core data.txt │ ├── 200-abstracting away core data.txt │ └── 300-core data in ui.txt ├── part1-bootstrapping │ ├── 500-boxes and items.txt │ ├── 400-use of tests.txt │ ├── 100-swift core data tests.txt │ ├── 300-view controller basics.txt │ ├── 600-functional tests.txt │ └── 200-fleshing out interfaces.txt ├── part9-epilogue.txt ├── part2-process │ ├── 400-viper.txt │ ├── 300-ports adapters.txt │ ├── 100-naive event handler.txt │ └── 200-naive add remove.txt └── part3-domain │ └── 100-events.txt ├── LICENSE.md └── README.md /manuscript/part-appendix/300-bonus.txt: -------------------------------------------------------------------------------- 1 | # Bonus Articles 2 | -------------------------------------------------------------------------------- /manuscript/part0-intro.txt: -------------------------------------------------------------------------------- 1 | 2 | {frontmatter} 3 | 4 | # Prologue 5 | 6 | -------------------------------------------------------------------------------- /manuscript/part-content.txt: -------------------------------------------------------------------------------- 1 | 2 | {mainmatter} 3 | 4 | 5 | -# The Story of Developing a Clean App by Example 6 | -------------------------------------------------------------------------------- /manuscript/images/title_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/title_page.jpg -------------------------------------------------------------------------------- /manuscript/images/201503130948_viper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201503130948_viper.jpg -------------------------------------------------------------------------------- /manuscript/images/201507230959_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201507230959_tree.png -------------------------------------------------------------------------------- /manuscript/images/201507231210_mvvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201507231210_mvvm.png -------------------------------------------------------------------------------- /manuscript/images/201507281218_trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201507281218_trees.png -------------------------------------------------------------------------------- /manuscript/images/201501051800_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201501051800_window.png -------------------------------------------------------------------------------- /manuscript/images/20141206190120_coredata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20141206190120_coredata.png -------------------------------------------------------------------------------- /manuscript/images/20141125103807_components.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20141125103807_components.jpg -------------------------------------------------------------------------------- /manuscript/images/201507231001_window-strip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201507231001_window-strip.png -------------------------------------------------------------------------------- /manuscript/images/201507231024_layered-hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/201507231024_layered-hex.png -------------------------------------------------------------------------------- /manuscript/images/20150723132836_viper-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20150723132836_viper-arch.png -------------------------------------------------------------------------------- /manuscript/images/20150723113926_layers-ports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20150723113926_layers-ports.png -------------------------------------------------------------------------------- /manuscript/images/20141204100034_ports-adapters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20141204100034_ports-adapters.png -------------------------------------------------------------------------------- /manuscript/images/20141117095632_target-membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20141117095632_target-membership.png -------------------------------------------------------------------------------- /manuscript/images/20141125103816_components--event_handler.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleanCocoa/mac-appdev-book/HEAD/manuscript/images/20141125103816_components--event_handler.jpg -------------------------------------------------------------------------------- /manuscript/part-appendix.txt: -------------------------------------------------------------------------------- 1 | 2 | {backmatter} 3 | 4 | -# Appendix 5 | 6 | The appendix is mostly a collection of blog post-sized writings about problems I discovered. Apart from the upcoming list of suggested reading, the articles are in no particular order. 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. -------------------------------------------------------------------------------- /manuscript/201412051558 multiple consumers.txt: -------------------------------------------------------------------------------- 1 | ## Multiple Consumers: Reaping the Benefits of Domain Events _(Upcoming!)_ 2 | 3 | I this chapter, I'll write: 4 | 5 | * Introduce multiple consumers: keeps 2 windows in sync (`Consumers`) 6 | * Add background task to increase the (yet unused) counter 7 | -------------------------------------------------------------------------------- /manuscript/sample-stripped.txt: -------------------------------------------------------------------------------- 1 | 2 | I> ## Sample Information 3 | I> 4 | I> Only a small selection of chapters are added to this chapter in this sample. I selected sections which I find most interesting to show you what is covered in the book. Because the sample is missing context, a few things might not make sense the way it is. 5 | -------------------------------------------------------------------------------- /manuscript/newsletter.txt: -------------------------------------------------------------------------------- 1 | 9 | 10 | {icon=gift} 11 | G> ## Get the Latest from the Lab 12 | G> 13 | G> Sign up to get a private preview of my upcoming programming books and other material to make you a better independent developer: 14 | G> 15 | G> 16 | G> 17 | -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | part0-intro.txt 2 | newsletter.txt 3 | sample-stripped.txt 4 | part0-intro/100-who.txt 5 | part0-intro/200-contribute.txt 6 | part0-intro/300-motivation.txt 7 | part0-intro/400-the example.txt 8 | part0-intro/500-architecture.txt 9 | 10 | part-content.txt 11 | 12 | part1-bootstrapping.txt 13 | sample-stripped.txt 14 | part1-bootstrapping/600-functional tests.txt 15 | 16 | part2-process.txt 17 | sample-stripped.txt 18 | part2-process/300-ports adapters.txt 19 | 20 | part3-domain.txt 21 | sample-stripped.txt 22 | part3-domain/100-events.txt 23 | 24 | part-appendix.txt 25 | sample-stripped.txt 26 | part-appendix/100-links.txt 27 | part-appendix/200-books.txt 28 | -------------------------------------------------------------------------------- /manuscript/README.md: -------------------------------------------------------------------------------- 1 | # How to use this manuscript 2 | 3 | This is a [Leanpub](http://leanpub.com) book manuscript. Thus, `Book.txt` is the book index which is used to assemble the finished text from all available fragments. `Sample.txt` is used in a similar fashion to create a sample book preview. 4 | 5 | I put each part's introduction and heading in a file beginning with `part-`. That's just a personal preference. 6 | 7 | The rest of the files start with [a timestamp ID][id]. This means they are sorted by date of creation, not by appearance in the book. This will almost certainly cause confusion as long as you're not used to a writing approach this much fragmented. If you're curious: I write about the [Zettelkasten method][zk] elsewhere. 8 | 9 | [id]: http://christiantietze.de/posts/2014/02/add-identity/ 10 | [zk]: http://zettelkasten.de 11 | -------------------------------------------------------------------------------- /manuscript/part3-domain.txt: -------------------------------------------------------------------------------- 1 | # Part 3: Putting a Domain in Place 2 | 3 | For the most part of this exercise, the Domain consisted of two **Entities**: `Box` and `Item`. It wasn't a domain model worth talking about. There were data containers without any behavior at all. There were no business rules except managing `Item`s in `Box`es. 4 | 5 | This changed a bit when I realized I had mixed **Domain Service** and **Application Service** into a single object. The resulting `ProvisioningService` in the domain creates Entities, adds them to the repository, and notifies interested parties of the event. 6 | 7 | Notifications are useful for auto-updating the view as I mentioned in the last part already. They are useful for populating an event store, too: persist the events themselves instead of Entity snapshots to replay changes and thus synchronize events across multiple clients, for example. This is called [Event Sourcing][esrc] and replaces traditional database models to persist Entity states. Digging into this goes way beyond the scope of this book, but I'm eager to try it in the future (hint, hint). 8 | 9 | [esrc]: http://msdn.microsoft.com/en-us/library/dn589792.aspx 10 | -------------------------------------------------------------------------------- /manuscript/part4-coredata.txt: -------------------------------------------------------------------------------- 1 | # Part 4: Using Core Data for Convenience 2 | 3 | Finally, after all we have achieved, I explore how to use Core Data managed objects the way Apple meant them to be used: woven deeply into the core of our application. 4 | 5 | Until now, we've avoided this with utmost caution. Let's see how bad it'll be if we turn to the _Dark Side._ 6 | 7 | `NSManagedObject` exposes more than 20 methods and nearly 10 properties. None of these are relevant for the rest of our application except the repository. 8 | 9 | During the rest of this book, I've tried to build the application as much "bottom up" as possible, given that I knew which persistence mechanism I was going to use. The notion of a Domain Model is, in essence, the abstraction of everything that has to do with the infrastructure and user interface APIs. I like that. Working with code in isolation is very rewarding: everything is under your control. 10 | 11 | Now, I'm going to put the Core Data managed objects first and see what I can do to keep the app well managed and the meager business logic isolated. 12 | 13 | I keep track of progress in a separate branch on GitHub called [`core-data-only`](https://github.com/CleanCocoa/mac-appdev-code/tree/core-data-only). The end result is merged into the main project as a separate target with a separate folder. 14 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | 2 | part0-intro.txt 3 | newsletter.txt 4 | part0-intro/100-who.txt 5 | part0-intro/200-contribute.txt 6 | part0-intro/300-motivation.txt 7 | part0-intro/400-the example.txt 8 | part0-intro/500-architecture.txt 9 | part0-intro/600-patterns.txt 10 | 11 | part-content.txt 12 | 13 | part1-bootstrapping.txt 14 | part1-bootstrapping/100-swift core data tests.txt 15 | part1-bootstrapping/200-fleshing out interfaces.txt 16 | part1-bootstrapping/300-view controller basics.txt 17 | part1-bootstrapping/400-use of tests.txt 18 | part1-bootstrapping/500-boxes and items.txt 19 | part1-bootstrapping/600-functional tests.txt 20 | 21 | part2-process.txt 22 | part2-process/100-naive event handler.txt 23 | part2-process/200-naive add remove.txt 24 | part2-process/300-ports adapters.txt 25 | part2-process/400-viper.txt 26 | 27 | part3-domain.txt 28 | part3-domain/100-events.txt 29 | part3-domain/200-error handling.txt 30 | 31 | part4-coredata.txt 32 | part4-coredata/100-changes for core data.txt 33 | part4-coredata/200-abstracting away core data.txt 34 | part4-coredata/300-core data in ui.txt 35 | 36 | part9-epilogue.txt 37 | part9-epilogue/100-assessment.txt 38 | 39 | part-appendix.txt 40 | part-appendix/100-links.txt 41 | part-appendix/200-books.txt 42 | part-appendix/300-bonus.txt 43 | newsletter.txt 44 | part-appendix/400-unowned self in closures.txt 45 | part-appendix/500-objc api.txt 46 | part-appendix/600-on swift.txt 47 | part-appendix/700-adopting swift 2.txt 48 | newsletter.txt 49 | -------------------------------------------------------------------------------- /manuscript/part0-intro/400-the example.txt: -------------------------------------------------------------------------------- 1 | ## The Example 2 | 3 | This book is driven by example. Every pattern used will be put to practice in code. 4 | 5 | The example project and this very book you read are the result of me trying to solve a problem in the domain of the Word Counter for Mac. This project is a model, so to speak, to explore solutions. 6 | 7 | ![The tree of entities and a window screenshot](images/201507281218_trees.png) 8 | 9 | Let's pretend we want to create an application to organize stuff. It manages items. Items reside inside boxes. Users may create boxes and put items inside them. Boxes may only contain items but no other boxes. Items may contain nothing else. It's up to the user to decide what an item is, and she may label each box individually, too. 10 | 11 | The resulting data can be represented as a very shallow tree structure. That's why we want to display it as a tree view component, using an `NSOutlineView`. 12 | 13 | Additionally, we decide to use Core Data for persistence. 14 | 15 | The corner stones of the application are set. This is a stage of a lot of app visions when they are about to become a reality: not 100% concrete, not thoroughly designed, but you know the basic course of action. Then you get your hands dirty, explore, and learn. I don't want to advocate a head-over-heels approach here. There's a lot of useful planning you can do in advance that won't prevent innovation later. But this book is not the book to teach you that; this book is aimed to take people from a vision and semi-concrete implementation plan to the next level. 16 | -------------------------------------------------------------------------------- /manuscript/part9-epilogue/100-assessment.txt: -------------------------------------------------------------------------------- 1 | ## Objectives Accomplished? 2 | 3 | In the introduction, I told you about the learning objectives I had in mind: 4 | 5 | 1. Your will learn to recognize the core of your application, the Domain, where the business rules belong. 6 | 2. Consequently, you will learn how to put your view controllers on a diet and to mentally think of them as a user interface detail. 7 | 3. In the end, you'll be comfortable with using your own "POSO"s (Plain Old Swift Objects) instead of looking for a solution in the Cocoa framework. 8 | 9 | Now please take a few minutes and answer for yourself a few questions to solidify the new information and to find out where additional information is beneficial. 10 | 11 | ### Self-Assessment 12 | 13 | Please write down your answers in any way you like just to make sure your mind doesn't skip a thing and to enhance the learning effect. 14 | 15 | These are the questions I suggest: 16 | 17 | * Which were your breakthroughs, your personal Eureka-moments? 18 | * What did you learn about how you write code? 19 | * What are you comfortable with doing now but couldn't think of earlier? 20 | * Which of the three objectives do you think I did not help accomplish? 21 | * Which opportunity have I missed? What should I have addressed? 22 | 23 | Thank you for taking the time to help make this book worth your while! 24 | 25 | ### Feedback 26 | 27 | I'd love to hear from you and find out what you have learned. You may event mail me your reflection if you want. 28 | 29 | Visit the [project website][hp-contact] and say hi! 30 | 31 | [hp-contact]: http://divinedominion.github.io/mac-appdev-book/#contact 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Exploring Mac App Development Strategies 2 | ======================================== 3 | 4 | This is the book manuscript for "[Exploring Mac App Development Strategies][leanpub]". 5 | 6 | [Example code](https://github.com/CleanCocoa/mac-appdev-code) is on GitHub, too. 7 | 8 | Outline 9 | ------- 10 | 11 | * Introduction, and my motivation to document the process 12 | * Part 1: Bootstrapping 13 | * Getting comfortable with Swift, porting Objective-C code to recreate the problem space 14 | * Get Core Data and related tests running 15 | * Prepare the user interface in its own layer 16 | * Have tests in place for the app's domain 17 | * Part 2: Solving the actual problem 18 | * My first, naive take 19 | * Ports & Adapters-style solution 20 | * Part 3: Putting a Domain in Place 21 | * Sending Domain Events 22 | * Handling Errors 23 | * Doing real work in the background 24 | * Part 4: Using Core Data for Convenience 25 | * Using Protocols to Hide NSManagedObject Internals 26 | * Using Cocoa Bindings with Core Data 27 | * Epilogue 28 | * Appendix 29 | * Interesting links 30 | * On Swift 31 | * About Objective-C API compliance-related problems 32 | 33 | License 34 | ======= 35 | 36 | Creative Commons License
Develop Mac Apps With a Clean Architecture and Swift by Christian Tietze is licensed under a Creative Commons Attribution 4.0 International License. 37 | 38 | [leanpub]: http://leanpub.com/develop-mac-apps-clean-architecture-swift 39 | 40 | -------------------------------------------------------------------------------- /manuscript/part-appendix/400-unowned self in closures.txt: -------------------------------------------------------------------------------- 1 | ## Strong Reference Cycles 2 | 3 | [Swift closures][closures] are prone to creating strong reference cycles when you keep the closure around and reference self from within the closure: 4 | 5 | {linenos=off} 6 | let closure = { 7 | self.doSomething() 8 | } 9 | 10 | Thanks to ARC, Xcode will complain if you omit `self` in this case, although you can invoke `doSomething()` without further ado from everywhere else in the same class. That's true if you use closures for lazy initialized properties, too: 11 | 12 | {title="Illustrating reference cycles. Lazy properties are safe"} 13 | class Person { 14 | // Strong reference to the closure 15 | var onWakeUp: () -> Void = {} 16 | 17 | let firstName: String 18 | let lastName: String 19 | 20 | // Closure is not retained after evaluation, no need to worry 21 | lazy var fullName: String = { 22 | return "\(self.firstName) \(self.lastName)" 23 | }() 24 | 25 | init(firstName: String, lastName: String) { 26 | self.firstName = firstName 27 | self.lastName = lastName 28 | 29 | // Use `weak self` to break strong reference cycle 30 | onWakeUp = { [weak self] in 31 | self.dressUp() 32 | } 33 | } 34 | 35 | func dressUp() { /* ... */ } 36 | } 37 | 38 | The main takeaway for me at this point: 39 | 40 | - Lazily initialized properties aren't strongly referenced closures. No need to worry about strong reference cycles. 41 | - Closures capture their context and thus capture a strong reference to `self`, too. There, I have to pipe in `[weak self]`. 42 | 43 | [closures]: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-XID_151 44 | [uoself]: http://stackoverflow.com/questions/24320347/shall-we-always-use-unowned-self-inside-closure-in-swift -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping.txt: -------------------------------------------------------------------------------- 1 | # Part 1: Bootstrapping -- Setting Up the Project, Core Data, and Unit Tests 2 | 3 | Swift is still pretty new. Since it's so new, it's likely you haven't had a chance to use it seriously. After all, investing in a hip new language is pretty risky: it takes time and might not lead you anywhere. 4 | 5 | With Swift, it's a safe bet to stay a Cocoa developer in the future. Apple pushes the language forward, so there's a lot of momentum already. Swift is not in the same situation Java was in during the late 1990's. 6 | 7 | Initially, you may not know if errors happen because you can't use Swift properly, or if it's an actual implementation problem. I guess having existing code to port makes it easier to take Swift for a test drive. Without that luxury, you'll have to learn faster. 8 | 9 | That's why we're going to start with a _natural_ approach to learning Swift and getting an application up and running. 10 | 11 | This part of the book is not about designing and crafting the application in a "clean" manner. There's no up-front design. This part is about understanding the new ecosystem of Swift and how it integrates with Xcode and existing Cocoa frameworks. It's an exploratory part: I think you'll be able to follow along with the resistance I encountered and how I thought and coded my way out of dead-ends better in this style. 12 | 13 | I dedicate this part to making the switch to Swift and getting everything to run, including: 14 | 15 | 1. Get Core Data and related tests running 16 | 2. Prepare the user interface in its own layer 17 | 3. Have tests in place for the app's domain 18 | 19 | In the second part, I'll cover the actual application's functionality. We'll be going to explore a lot of architectural patterns in-depth there. 20 | 21 | This part is mostly made up of longer notes or journal entries. They aren't meant to tell a coherent story. They are meant to provide a view over my shoulder, include details and background information so you can follow along easily. 22 | 23 | Think of this part as a log book to follow the path to adopting Swift. You may want to skim most of this part if you're comfortable with Swift, XCTest, and Core Data already. 24 | -------------------------------------------------------------------------------- /manuscript/part2-process.txt: -------------------------------------------------------------------------------- 1 | # Part 2: Sending Messages Inside of the App 2 | 3 | I tried to prepare all of the app's components so far in isolation and not worry much about the integration. In other words, running the app will show that things don't seem to work. The basic structure is in place, but there's virtually no _action._ That's changing in this part. 4 | 5 | The app window is going to look like this: 6 | 7 | ![Final App Window](images/201501051800_window.png) 8 | 9 | At the moment, the state of the app is the following: 10 | 11 | * The user interface is operational but doesn't persist data. The `ItemViewController` reacts to button presses and creates node objects. 12 | * The Core Data part of infrastructure seems to work fine according to the tests but doesn't receive any commands, yet. 13 | * The domain is particularly boring. It has no behavior at all. Until now, the whole demo application is centered around getting user input to the data store. This will not change until the next part. It's nonsensical to work this way of course when the domain should be the very key component of the application. 14 | * There's no event handling at all, as in "User adds a Box to the list". There's no layer between user interface and domain. 15 | 16 | ![Components](images/20141125103807_components.jpg) 17 | 18 | This part will focus on the integration and various methods of passing messages between components of the app. When I figured out the _structure_ of the app in the last sections, now I worry more about designing the _processing_ of information and performang actions. There are a few attractive options to consider: 19 | 20 | * Cocoa's classic delegate pattern to obtain objects ("data source") and handle interactions ("delegate") 21 | * Ports & Adapters-style command-only interfaces, adhering to CQRS[^cqrs] 22 | * Leveraging Domain Events (since there's no domain worth speaking of, we'll defer exploring that option to part 3) 23 | 24 | We'll look at all of these in detail. 25 | 26 | [^cqrs]: According to "Command--Query Responsibility Segregation" you separate changes (commands) from obtaining data (queries). A method should return a value or cause a change in the system, never both. 27 | -------------------------------------------------------------------------------- /manuscript/part-appendix/100-links.txt: -------------------------------------------------------------------------------- 1 | # Further Reading 2 | 3 | ## Interesting Links 4 | 5 | You should really check out the following: 6 | 7 | SourceView sample code (Objective-C) 8 | : 9 | : Example application by Apple where `NSOutlineView` is used. Good for getting started, although the example app doesn't run properly anymore -- I don't see any images in the source view. 10 | 11 | Clean Architecture 12 | : 13 | : 14 | : Uncle Bob's explanation of the architectural concepts I tried to adhere to. Goes beyond Hex 15 | 16 | Uncle Bob: Architecture -- The Lost Years (Video) 17 | : 18 | : This talk introduced me to Hexagonal and Clean Architecture. 19 | 20 | Jim Gay: Clean (Ruby) Code 21 | : Video: [East-Oriented Code](http://confreaks.com/videos/4825-RubyConf2014-eastward-ho-a-clear-path-through-ruby-with-oo) at RubyConf 2014 22 | : Book: ["Clean Ruby"](http://clean-ruby.com) -- full of good examples which you can apply to Swift, too, and even more so to Objective-C and its meta-programming capabilities. 23 | 24 | Avdi Grimm: Objects on Rails 25 | : 26 | : A free e-book which taught me how to decouple my application from Ruby on Rails. This one got me hooked on thinking past using the framework properly. 27 | 28 | Other links of interest on the topic of software architecture: 29 | 30 | * ["250 Days Shipping With Swift and VIPER"](https://realm.io/news/altconf-brice-pollock-250-days-shipping-with-swift-and-viper/) (Video) 31 | * ["Integrated Tests are a Scam"](https://vimeo.com/80533536) (Video) 32 | * "Advanced iOS Application Architecture and Patterns", Session 229 at [WWDC 2014](https://developer.apple.com/videos/wwdc/2014/) (Video) 33 | * ["Building Better Apps with Value Types in Swift"](https://developer.apple.com/videos/wwdc/2015/?id=414) (Video) at WWDC 2015 34 | * [What service objects are not](https://medium.com/@KamilLelonek/what-service-objects-are-not-7abef8aa2f99), with Ruby examples 35 | -------------------------------------------------------------------------------- /manuscript/part0-intro/200-contribute.txt: -------------------------------------------------------------------------------- 1 | ## Contributing to the Book Manuscript and Example Project 2 | 3 | I believe in empowering people. As a consequence, I also believe in sharing information. That's why I am open-sourcing the whole book plus example code. You can pay for the packaged product and support my writing, but the content will be freely available for every brave soul trying to make sense of Mac app development. 4 | 5 | Your feedback is very welcome, and I appreciate pull requests on GitHub for both the manuscript and the example project: if you spot a typo or a code error, help me fix it! You can also open issues on GitHub to request additional "features:" more details, more explanations, more examples. 6 | 7 | So feel free to contribute to this book and the overall project via GitHub: 8 | 9 | - [Book Manuscript on GitHub](https://github.com/CleanCocoa/mac-appdev-book) 10 | - [Project Code on GitHub](https://github.com/CleanCocoa/mac-appdev-code) 11 | 12 | You can read more about the book project on its [dedicated website](http://cleancocoa.com/books/exploring-mac-app-dev/). 13 | 14 | A huge thanks for providing feedback and fixing bugs goes to: 15 | 16 | - Jorge D Ortiz Fuentes 17 | - [Stanislaw Pankevich](https://github.com/stanislaw) 18 | 19 | ### A Note on Reading the Example App Code 20 | 21 | Developing an app is an iterative process. Looking at the code of a finished app often leaves you wondering: how did he get to this point? 22 | 23 | Experimental code from the first commits won't be available in the final version, of course, because I've surpassed the problems along the way. I leverage the version history of the example app to show the app at every stage of development so you can follow along. The git history becomes a tool to understand the development. Throughout the book, I include links to commits on GitHub so you can checkout the code at a particular stage in the narrative. 24 | 25 | When you visit GitHub and download the code as a Zip file, though, you will see the final version. This final version will not reflect intermediate steps of the development process. Refer to the footnotes and the links to git commits if you find you need more context. I explain a lot of the refactorings and discuss most code changes in the book itself so you can follow along easily. 26 | 27 | If you cannot understand what a section is about because there's not enough code to illustrate what's going on, simply open an issue on GitHub in the book manuscript repository and I'll revise this in a future edition! 28 | 29 | -------------------------------------------------------------------------------- /manuscript/part-appendix/500-objc api.txt: -------------------------------------------------------------------------------- 1 | ## Access Rights VS Laissez-faire Objective-C 2 | 3 | The following class hierarchy works well in view controller code until you throw in a `TestNode` subclass in your tests to stub a `BoxNode`, say.: 4 | 5 | @objc(TreeNode) 6 | public protocol TreeNode { 7 | var title: String { get set } 8 | var count: UInt { get set } 9 | var children: [TreeNode] { get } 10 | var isLeaf: Bool { get } 11 | } 12 | 13 | // No need to make this public as only the view needs to know about it 14 | class BoxNode: NSObject, TreeNode { 15 | let boxId: BoxId 16 | /* ... */ 17 | } 18 | 19 | class ItemNode: NSObject, TreeNode { /* ... */ } 20 | 21 | Then, downcasting to `BoxNode` in production code results in a runtime error. But you _have_ to downcast in order to access the `boxId` property: since Swift doesn't support the inherently unsafe `performSelector:withObject:` which in the past worked on objects of type `id` just fine, there's no other way to get to properties not exposed via the type. 22 | 23 | Two options: 24 | 25 | 1. Add an abstract `identifier` property to `TreeNode`. That's clean, but in order to create the `Identifiable` protocol for `BoxId` and `ItemId`, you have to get rid of `struct`s in favor of `class`es: `Identifiable` has to be annotated with `@objc` to work with `TreeNode`, but then only classes may implement it. Gone are the call-by-value benefits of `struct`s. I don't like that. 26 | 2. Make `BoxNode` and `ItemNode` public. They aren't to be used by any client code, really, but only by exposing them can you inherit their properties in `TestBoxNode` objects, say. 27 | 28 | Thus I end up with too many public interfaces and a test stub looking like this: 29 | 30 | {title="Not-so-concise test replacement class"} 31 | class TestBoxNode: BoxNode { 32 | convenience init() { 33 | self.init(boxId: BoxId(0)) 34 | 35 | title = "title" 36 | count = 1234 37 | children = [] 38 | isLeaf = false 39 | } 40 | 41 | convenience init(title: String) { 42 | self.init() 43 | self.title = title 44 | } 45 | } 46 | 47 | Sheesh. If only Swift was able to use Cocoa API without Objective-C: then I'd prefer option (1) and keep the structs. 48 | 49 | I think the `Identifiable` protocol works well to communicate the intent, though, so I add it nevertheless. But it's not as useful right now as it could be with all the Objective-C bridging limitations. 50 | -------------------------------------------------------------------------------- /manuscript/part0-intro/100-who.txt: -------------------------------------------------------------------------------- 1 | ## Who Is This Book For? 2 | 3 | You have all the best intentions but no clue how to make them a reality: You want to write clean code and you don't want to end up with ugly and unmaintainable applications. 4 | 5 | I was stuck in that same situation. I wanted to write my own apps and found my needs quickly surpassed the guidance of Apple's documentation and sample codes. Both are a useful resource, but you won't learn how to scale and create a complex application. 6 | 7 | Typical questions include: 8 | 9 | * How do you architect a complex application? 10 | * How do you deal with large databases? 11 | * How should you implement background services? 12 | 13 | It's a popular advice that you should not stuff everything into your view controllers to avoid suffering from _massive view controller_ syndrome. But nobody show how that works in apps. Maintainable code is important for an app that grows, so how do you get there? 14 | 15 | I focus on laying the foundation for an answer to the first question, how to architect Mac apps. I won't tell you how to scale your database and how to split your app into multiple services in this book. (But check the website from time to time, because I've got something in the making!) 16 | 17 | To read this book, you don't need to be a Cocoa wiz. You don't even need to be proficient with either Swift or Objective-C: I don't use crazy stuff like method swizzling or operator overloading or anything that's hard to grasp, really. When I wrote the first edition of this book in 2014, I just learned Swift myself. So there's lots of explanation and sense-making along the way. Chances are you already know Swift better required to understand the book. 18 | 19 | Here's what I hope you will learn from this book: 20 | 21 | * Your will learn to recognize the core of your application, the Domain, where the business rules belong. This is the very core of your application and it has no knowledge of UIKit, AppKit, whatever-kit -- it's all yours, custom made. 22 | * Consequently, you will learn how to put your view controllers on a diet. We will consider them a mere user interface detail. 23 | * Along the way, you'll get comfortable with creating your own "POSO"s (Plain Old Swift Objects) that convey information and encapsulate behavior instead of desperately looking for canned solutions. 24 | 25 | In short, the message of the book is: don't look for help writing your app. Learn how to create a solid software architecture _you_ are comfortable with instead, and plug in components from third parties where necessary. 26 | 27 | -------------------------------------------------------------------------------- /manuscript/part-appendix/200-books.txt: -------------------------------------------------------------------------------- 1 | 2 | {pagebreak} 3 | 4 | ## Interesting Books {#booklist} 5 | 6 | I recommend reading the following books, ordered by subjective significance to the topic. 7 | 8 | {begin-hanging-paragraphs} 9 | 10 | 11 | Vaughn Vernon (2013): _Implementing domain driven design_, Upper Saddle River, NJ: Addison-Wesley. 12 | --- _Lots of practical examples and explanation for the patterns Evans laid out._ 13 | 14 | 15 | Eric Evans (2006): _Domain-Driven Design. Tackling complexity in the heart of software_, Upper Saddle River, NJ: Addison-Wesley. 16 | --- _Full of good examples and refactorings itself but a little light on actually solving implementation problems._ 17 | 18 | 19 | Michael C. Feathers (2011): _Working effectively with legacy code_, Upper Saddle River, NJ: Prentice Hall Professional Technical Reference. 20 | --- _Helps to learn decoupling code incrementally and how to test hard-to-test parts. That's where I learned to provide means to reset singletons, and that it's better to wrap `NotificationCenter` in order to replace the wrapper in tests than not test notifications at all._ 21 | 22 | 23 | Steve Freeman and Nat Pryce (2010): _Growing object-oriented software, guided by tests_, Boston: Pearson Education. 24 | --- _This book has taught me so much about Test-Driven Development! I figured out when to use functional tests, why it's beneficial to start with a failing test on the system level to guide development, and how to create multi-layered applications with test fakes all thanks to this book._ 25 | 26 | 27 | {pagebreak} 28 | 29 | 30 | Ivar Jacobson, Magnus Christerson, Patrik Jonsson, and Gunnar Övergaard (1990): _Object-Oriented Software Engineering. A Use Case Driven Approach_, Wokingham: Addison-Wesley. 31 | --- _Actually a recommendation by Uncle Bob in _Architecture: The Lost Years_. It taught me to think about application services and use case objects. The book's focus is on architecture, not on code. Useful practices. You may get this one used for a few dollars. I found it in my local university's library._ 32 | 33 | 34 | Scott Millett (2014): _Practicing Domain-Driven Design. Practical advice for teams implementing the development philosophy of Domain-Driven Design. With code examples in C# .NET_, Scott Millett. 35 | 36 | 37 | Robert C. Martin (2009): _Clean Code. A Handbook of Agile Software Craftsmanship_, Upper Saddle River: Prentice Hall. 38 | 39 | 40 | David West (2004): _Object thinking_, Redmond, Wash.: Microsoft Press. 41 | 42 | 43 | Andy Oram and Greg Wilson (Eds.) (2007): _Beautiful Code_, Beijing: O'Reilly. 44 | 45 | 46 | Sandi Metz (2013): _Practical object-oriented design in Ruby: an agile primer_, Upper Saddle River, NJ: Addison-Wesley. 47 | --- _A really good read. Sadly, it's about Ruby, not about Swift or Objective-C, but you may want to peek into it anyway._ 48 | 49 | {end-hanging-paragraphs} 50 | -------------------------------------------------------------------------------- /manuscript/part-appendix/700-adopting swift 2.txt: -------------------------------------------------------------------------------- 1 | ## Adopting Swift 2.0 Errors 2 | 3 | The Swift application template for a Core Data application looked awful at first. The language was new, so it was hard to read. Also, Swift 1.0 was way more clunky than Swift 2.0 is now. 4 | 5 | In order to set up a `PersistentStack`, the `AppDelegate` was meant to create the store URL: 6 | 7 | {linenos=off} 8 | let storeURL = self.applicationDocumentsDirectory 9 | .URLByAppendingPathComponent("ItemModel.sqlite") 10 | 11 | `applicationDocumentsDirectory` is a lazy property. Before it's initialized, the `AppDelegate` ensures that the resulting path actually points to an existing directory. That's when I added this guard clause: 12 | 13 | {linenos=off} 14 | guardApplicationDocumentsDirectory(directory) 15 | 16 | That method, though, became a 40 line long monster of entangled conditions, mostly because file operations in general can fail with an error. 17 | 18 | With Swift 2.0, that became even worse.[^appdel] Until this pain turned into feedback and forced me to refactor the code. Protocol extensions aside, error handling will be the one big change in your project. 19 | 20 | You know by now that optionals essentially are `Optional` enum instances. You can think of the addition of `throw` to change a function's return value of, say, `String`, to an enum called `Either`. The do-catch construct then is a `switch` statement with the usual pattern matching. 21 | 22 | That's how [the `Result` enum][result] helped Swift programmers -- until 2.0 arrived. It's how the Haskell programmers work with errors. 23 | 24 | Since we can make it so error throwing propagates, it should suffice to catch errors at the highest level possible: 25 | 26 | private func guardApplicationDocumentsDirectory(directory: NSURL) { 27 | do { 28 | if try !directoryExists(directory) { 29 | try createDirectory(directory) 30 | } 31 | } catch let error as NSError { 32 | NSApplication.shared().presentError(error) 33 | abort() 34 | } 35 | } 36 | 37 | In both `directoryExists(_:)` and `createDirectory(_:)` of the `AppDelegate`, I wrap errors of the system in my own to add context, then I re-throw these. 38 | 39 | For example: 40 | 41 | private func createDirectory(directory: NSURL) throws { 42 | let fileManager = NSFileManager.defaultManager() 43 | 44 | do { 45 | try fileManager.createDirectoryAtPath(directory.path!, 46 | withIntermediateDirectories: true, attributes: nil) 47 | } catch let fileError as NSError { 48 | 49 | var userInfo = [NSObject : AnyObject]() 50 | userInfo[NSLocalizedDescriptionKey] = 51 | "Failed to create the application documents directory" 52 | userInfo[NSLocalizedFailureReasonErrorKey] = 53 | "Creation of \(directory.path) failed." 54 | userInfo[NSUnderlyingErrorKey] = fileError 55 | 56 | throw NSError(domain: kErrorDomain, 57 | code: 1, 58 | userInfo: userInfo) 59 | } 60 | } 61 | 62 | Without wrapping the error, a simple method suffices: 63 | 64 | private func createDirectory(directory: NSURL) throws { 65 | 66 | let fileManager = NSFileManager.defaultManager() 67 | try fileManager.createDirectoryAtPath(directory.path!, 68 | withIntermediateDirectories: true, attributes: nil) 69 | } 70 | 71 | Since Swift requires us to add the `try` keyword in front of every call to a throwing function, there's no way to overlook points of potential failure. 72 | 73 | [^appdel]: See what Xcode 7's Swift 2.0 conversion tool made out of that, surrounding failing calls with do-try-catch, [on GitHub](https://github.com/CleanCocoa/mac-appdev-code/blob/06f6416a4a084182e9a9530e9592c79e6954b91d/DDDViewDataExample/AppDelegate.swift). 74 | 75 | [result]: http://nomothetis.svbtle.com/error-handling-in-swift 76 | -------------------------------------------------------------------------------- /manuscript/part4-coredata/100-changes for core data.txt: -------------------------------------------------------------------------------- 1 | ## How to Change the Project to Put Core Data at It's Center 2 | 3 | Most of the work is _renaming_ existing objects. 4 | 5 | * `Box` and `Item` from the core model are obsolete; their behavior will move into `ManagedBox` and `ManagedItem` respectively. 6 | * The "Managed-" prefix becomes obsolete. 7 | * Tests have to be changed, because there's no way to create `Box` or `Item` objects without an `NSManagedObjectContext` anymore. In essence, every model-centered test now becomes a `CoreDataTestCase`. 8 | 9 | Most of the renaming is a pretty safe change. Lean onto the compiler for guidance. I started with `Item`/`ManagedItem` to keep things isolated for a while, but I reached firm ground only after the `Box`/`ManagedBox` conversion was finished. 10 | 11 | Since creating objects during tests is now a lot more complex than invoking an object's initializer, I added yet another test class which sports a `CoreDataBoxRepository` instance most of the test suites needed anyhow: 12 | 13 | class BoxCoreDataTestCase: CoreDataTestCase { 14 | var repository: CoreDataBoxRepository! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | repository = CoreDataBoxRepository(managedObjectContext: context) 19 | } 20 | 21 | func createBoxWithId(boxId: BoxId, title: String) { 22 | Box.insertBoxWithId(boxId, title: title, 23 | inManagedObjectContext: context) 24 | } 25 | 26 | func createAndFetchBoxWithId(boxId: BoxId, title: String) -> BoxType? { 27 | createBoxWithId(boxId, title: title) 28 | 29 | return repository.boxWithId(boxId) 30 | } 31 | } 32 | 33 | This solves part of the problem. Creating an `Item` is tricky. 34 | 35 | In the past, test cases could simply create an `Item` instance and add it to the box: 36 | 37 | {linenos=off} 38 | let itemId = ItemId(6789) 39 | let item = Item(itemId: itemId, title: "the item") 40 | box.addItem(item) 41 | 42 | The `ProvisioningService` of the app did the same. 43 | 44 | Behind the scenes, a `ManagedBox` would observe changes to the item collection of the box and persist these. It was a pain to set up initially, but it worked pretty well afterwards. The persistence through Core Data was hidden from the Domain Model objects. 45 | 46 | Now that Core Data is on the surface level, a naive take will look like this: 47 | 48 | {linenos=off} 49 | let itemId = ItemId(6789) 50 | let item = Item.insertItemWithId(itemId, title: "the item", 51 | inManagedObjectContext: context) 52 | box.addItem(item) 53 | 54 | That works well for tests since they have a `context` property. The `ProvisioningService` doesn't, though. Most of the application will not (and should not!) realize that Core Data is at play. 55 | 56 | It's pretty easy to get around this limitation: add more behavior to `Box` and make its API more meaningful! 57 | 58 | extension Box { 59 | func addItemWithId(itemId: ItemId, title: String) { 60 | 61 | Item.insertItemWithId(itemId, title: title, intoBox: self, 62 | inManagedObjectContext: managedObjectContext!) 63 | } 64 | } 65 | 66 | Tests and service objects of the app can now both use this simple method to get rid of all the set up work. The code of the example test case from above is now written as: 67 | 68 | {linenos=off} 69 | let itemId = ItemId(6789) 70 | box.addItemWithId(itemId, title: "the item") 71 | 72 | Client code knows nothing about Core Data, and since a `Box` instance is a `NSManagedObject`, it has a `managedObjectContext` property anyway. We don't even need to query the persistent stack object in any additional location in production code. 73 | 74 | The takeaway at this point is: even when you build your model using Core Data, you can adopt good object-oriented programming practices and create intent-revealing APIs. 75 | 76 | But right now, the API of `Box` and `Item` reveals too much; by the way of abstraction, we will be able to improve the situation even more. 77 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/500-boxes and items.txt: -------------------------------------------------------------------------------- 1 | ## Finally, Add `Box`es to the Strictly `Item`-Centric Application 2 | 3 | A lot of stuff seems to work quite right now. To make it more interesting, I finally add `Box`es to the example project. Recall that a `Box` contains various `Item`s but no other `Box`. 4 | 5 | From now on I find how nice Swift is for modeling data containers. Creating an explicit view model requires far less boilerplate code. 6 | 7 | With Objective-C, I had to provide custom initializers for a general `TreeNode` or `BaseNode` to distinguish "box" from "item" type representations, perhaps by setting properties like `isLeaf` in `init` appropriately. Using Swift, I tend to model types for this that encapsulate the correct behavior. Instead of 1 type with various usage scenarios that require informal knowledge about the type, make constraints explicit with type specifications. Even though I end up with 3 types instead of 1, I still spend less time dealing with edge cases and inconsistencies because each class enforces its own consistency itself: 8 | 9 | @objc public protocol TreeNode { 10 | var title: String { get set } 11 | var count: UInt { get set } 12 | var children: [TreeNode] { get } 13 | var isLeaf: Bool { get } 14 | } 15 | 16 | open class BoxNode: NSObject, TreeNode { 17 | open dynamic var title: String = "New Box" 18 | open dynamic var count: UInt = 0 19 | open dynamic var children: [TreeNode] = [] 20 | 21 | // Denotes it's a Box to the NSTreeController 22 | public dynamic var isLeaf: Bool = false 23 | } 24 | 25 | open class ItemNode: NSObject, TreeNode { 26 | open dynamic var title: String = "New Item" 27 | open dynamic var count: UInt = 0 28 | open dynamic var children: [TreeNode] = [] 29 | 30 | // Denotes it's an Item to the NSTreeController 31 | public dynamic var isLeaf = true 32 | } 33 | 34 | Using `dynamic` here enables KVO but requires `@objc` annotation and inheriting from `NSObject`. This is a must for `NSObjectController` and Cocoa Bindings to work. Apart from that annotation, these class definitions provide necessary default values without any `init()` boilerplate necessary. 35 | 36 | Maybe I won't need to address different classes in the end. Maybe it'd suffice to properly set up a `BaseNode` in order to distinguish `Box`es from `Item`s in the view model. After all, it's a very trivial distinction, isn't it? 37 | 38 | The mental model of me, the programmer, is important, too. I have to read and understand what's going on. And you, dear reader, need to be able to understand this stuff, too. A 1:1 mapping of classes to your mental model is pretty simple. You don't need to keep edge cases or internal switches in mind to know which object does what. A `BoxNode` cannot denote an item if the code really does what it says. On the other hand, a generic `BaseNode` with a `isItem` switch would imply you know about this switch and keep its setting in mind when you read code. You need to decode a lot more and you need to acquire more implicit knowledge about the app to understand what's going on difference. (Same for me in 6 months!) It's easier if the object type says what you're dealing with. You'll benefit from this in the long run. Clean code often equals code reading like prose: it's transparent what's going on. 39 | 40 | The distinction between a box and item node is modelled explicitly as different types and thus is easy to understand. The alternate approach will only make you slower. Maybe not a lot slower for the time you remember what you've done, but in the long run. Your future self is just another reader of a foreigner's code. You don't save anything if you roll with 1 general type instead of the 3 types I used above. This is new to folks moving from Objective-C to Swift. Swift favors explicit declarations and distinguishing things at compile time over configuring objects to act as different things at runtime. 41 | 42 | Remember that optimization and refactoring can wait until later. I don't think I introduced unnecessary design complexity here but improved readability instead. It also won't make the code run slower. Even if your arcane Swift runtime knowledge indicates that I'm wrong about the performance implication in principle, you still have to prove that this is an actual performance bottleneck. Worry about saving CPU cycles when your performance tests indicate you have a problem. 43 | 44 | Okay, you could still say it's premature to model the distinction when no code relies on it. Fair enough. But it will in just a matter of commits. 45 | -------------------------------------------------------------------------------- /manuscript/part-appendix/600-on swift.txt: -------------------------------------------------------------------------------- 1 | ## On Swift 2 | 3 | I think Swift is Apple's move towards more advanced app architecture patterns. Consider the wonders generics do to [handling domain events](#domainevents), for example. Compared to dealing with the `NSDictionary` `userInfo` directly, domain event objects are a very convenient way to access event-specific information in a failure-proof manner. 4 | 5 | Objective-C did lend a lot of its flexibility from Smalltalk. Swift ditches everything related to sending messages to `nil`, and you better check twice if a dictionary actually contained the key--value-pair you expected. 6 | 7 | 8 | 9 | Remember what I pointed out in the section about [_East-Oriented Code_](#east): returning `nil` makes you return two things which conform to different APIs. To make optionals explicit is a useful change to simplify our APIs. 10 | 11 | Mattt Thompson already proclaimed that [the death of Cocoa][cocoadeath] is going to happen. Most of what we know from AppKit, UIKit, and all the other frameworks doesn't play well with Swift's strictness. When I write code in Swift, I try to eliminate optionals in methods because I think this move makes using the method in question easier. But Cocoa was designed with Objective-C in mind, and so we all have to unwrap optionals, double-check on return values, and pass `inout` error pointers here and there. 12 | 13 | I, too, guess that this is going to change in the future. When the language matures, new frameworks will emerge. Maybe just a Cocoa 2.0, Swift style. Maybe something else entirely, fusing Mac and iOS even more. We don't know yet, but it's likely, because Swift changed so much. 14 | 15 | [cocoadeath]: http://nshipster.com/the-death-of-cocoa/ 16 | 17 | Talking about _East-Oriented Code_ and Swift making optionals explicit, I think Swift does a truly great job enforcing to send commands instead of query for values. I think optionals are okay for properties, but they are less handy when it comes to return values. 18 | 19 | If a method may or may not produce a specific result, you can either return an optional value, which is the usual way to do things, or you return nothing (but `self`, maybe) and use callbacks or output ports. When a condition isn't met, simply stop execution before you reach the line invoking the callback. When execution takes longer and should be deferred to another thread, simply use the callback there. 20 | 21 | Favoring callbacks or output ports of any kind over return values makes it easier to branch processes off into concurrent threads. The client object has to be designed to deal with latency anyway because a callback isn't as reliably immediate as a return value. 22 | 23 | Transforming the example project's Application Services to favor delegation over returning values made me realize how well the code reads this way. I don't have to search the clients of a method to find out where the information goes. 24 | 25 | A query doesn't know who's listening. A command knows where the information is flowing. 26 | 27 | Of course we have to be cautious: being able to read a method well is nice. But understanding the overall architecture is important, too. Focusing on the method level can lead to sub-optimization, that is improving a part of the system at the expense of the system's overall quality. In plain English, it doesn't help if we can see which collaborating object is receiving a message as long as the overall flow is unclear. If you end up with a big pile of objects sending commands, you'll hardly understand what's happening. 28 | 29 | I'd say Objective-C is more forgiving than Swift. And thus Swift's strictness points out potential risks or sources of bugs. I think it's a pain to override private methods in test doubles. So I end up delegating sub-tasks to helper objects which can be replaced. This, in turn, makes me wonder why I didn't do it in the first place. Maybe it is because declaring classes in Objective-C was way more cumbersome. 30 | 31 | The advanced application architecture patters I think of revolve around creating little value objects and more service objects. 32 | 33 | You could do this in Objective-C, too, with the usual overhead of creating a `.h` and a `.m` file for each class you want to re-use and not keep private to a particular client's `.m` file. There's a huge difference between enabling developers to do a thing and making it easy for them to do it. If it's easier, it will be used more. 34 | 35 | Each language paves the way we think about programming problems. Swift, to me, is about making it easier to create classes and structs. And Swift is about favoring commands over optional return values. Thus, Swift is more about "Tell, Don't Ask", and preferring Hexagonal Architecture and clean & concise code. 36 | 37 | ### Update on Oct., 2016 38 | 39 | Swift 3 is here, and it brought us a new naming convention in Foundation. Instead of `NSNotificationCenter`, it's now just `NotificationCenter`. But not only did the "NS" prefix go; a lot of things changed to value types internally. Notification names aren't based on strings anymore but on a `Notification.Name`. What I like most is the change of API in Grand Central Dispatch: instead of `dispatch_async` and other C-style free functions, we now have proper types for that with object-oriented interfaces. `DispatchQueue.main.async` is so much nicer to read. And I prefer `DispatchTime.now() + .seconds(5)` to compute a delay over the Double-Int64-NSEC_PER_SEC-stuff any day. 40 | 41 | So we're already getting a Foundation 2.0. I'm very excited to see if Cocoa is going to change, too, and if we really get a UIKit/AppKit blend. It seems we needn't hold our breaths for 2017, though. 42 | -------------------------------------------------------------------------------- /manuscript/part9-epilogue.txt: -------------------------------------------------------------------------------- 1 | # Epilogue 2 | 3 | I hope you found the exercise of removing all the "architecture smell" from the code useful in the last past. It's supposed to show how the components I created can be reduced to simpler things you already know or will get to know through your work with the Cocoa frameworks. 4 | 5 | In the end, this book shows two different approaches: 6 | 7 | First, it started with a few principles to organize code and think about the structure and flow of an application. There were some problems on the way, like decoupling Core Data from the rest of the app, but they were solved in the spirit of the same principles that started the journey. That's an approach of architecture-first, where you perform rather minimalistic planning to guide the process. 8 | 9 | Second, deconstructing the code to remove most of the stuff the architecture did impose really shows how to change a Cocoa app to become more flexible for change. You just have to read the deconstruction process the other way around. I stopped before removing `BoxRepository` from the code base; I stopped just before the example collapsed into a simplistic demo of how to combine Cocoa Bindings and Core Data. That would be the stuff you find in Apple's documentation. The stage of the "CoreDataOnly" app retains a last bit of boundary management, separating database access from the user interface. It shows what it may look like if you cleaned your code base a tiny bit. Then roll back more of the changes I performed to see how you can further extract responsibilities into objects from existing code. That's the approach of improving existing code, of refactoring and changing the architecture later. 10 | 11 | Me, I have found out that I want to try to implement the `ManagedBox`/`Box` distinction in the Word Counter to put Core Data in the outermost layer because it works really well once set up. Years later I can tell it works very well and the resulting module is healthy. 12 | 13 | Separating the functionality into cleanly divided layers proves to be a huge win. When components are separated and well-factored, adding or changing functionality becomes a lot easier. Still, sometimes it may be better to stick to the Cocoa features for a while until you find the app grows in complexity. Not every tool needs to be well-architected. Small apps that won't change a lot over the years can benefit from a shorter "time to market," that is less development time. In the end, you have to weigh the pros and cons. To do so, you have to _know_ them first, though. 14 | 15 | ## Learning Through Writing 16 | 17 | Writing this book changed how I approached coding this example application. I know writing makes you think clearly; but I found it improved my code, too. 18 | 19 | Maybe writing is a tool you could make use of. There's not much going on in the Cocoa app developer community regarding field reports. How do you develop applications? What challenges did you face today? These are useful questions for you to answer and provide insight for your readers. It also helps to think about decisions and write down alternate paths. By pointing at alternatives throughout this book, I have discovered how hard it has been to stick to a plan without knowing the options. Writing about these options helped me sort things out. It reduced the stress of not knowing what might happen. 20 | 21 | 22 | ## The Use of Cocoa Bindings 23 | 24 | As we've seen in part 4, Cocoa Bindings are a great way to simplify user interface set-up. Bindings make most of the view controller boilerplate obsolete. 25 | 26 | If your model is KVC or Bindings compliant, that is. 27 | 28 | Keep in mind that the alternate approach I advocated throughout the rest of this book was to use view models. Using view models, you take the real entities and prepare them for display use. It's easy to see what the view model provides, and their public API reveals what the interface is going to use. This is the promise of the Model--View--View-Model (MVVM) pattern. 29 | 30 | Cocoa Bindings does not help to reason about your code. 31 | 32 | With Bindings, you pass the real entities around. Say you have a "person" entity with first name and last name, if you want to add a helper method which prepares a `fullName` for a view component, you only have two places left: the view controller and the entity itself. There's not much wiggle room to insert a presenter component to perform any pre-processing when you use Bindings. 33 | 34 | Your Core Data managed objects will be two things: the entities and the view models. That's the all too common default situation we're facing, by the way. You have to think your way out of these constraints. That's probably why MVVM became so popular. It shows how to think differently about a particular problem. Only now people apply MVVM everywhere, hoping to end up with clean code and a useful architecture, ending up in another kind of mess. 35 | 36 | The majority of this book is dedicated to helping you develop strategies to get past the point of _Big Ball of Mud_ design, of cruft and mess. If your app is simplistic like the sample app of this book, you can ditch most of the architectural principles because they provide so little value. You've seen the effect on the code in part 4. Over-architecting leads to code no-one needs. If you want to display a list of items, and that's it, just don't try to force a Domain Model into the app. It's not worth it. But now you can employ it when you find your application is _sufficiently complex_ to warrant a Domain Model. 37 | 38 | I hope to have provided evidence and examples for all these various aspects of creating a macOS application and you can now pick wisely which path your next app or your next big refactoring will take. 39 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/400-use of tests.txt: -------------------------------------------------------------------------------- 1 | ## Use of Automated Tests 2 | 3 | Considering the circumstances and the high uncertainty of my situation, tests have more benefits than verifying the behavior of components: 4 | 5 | * Regression Tests: they ensure I don't break the functionality. 6 | * Discovery Tests: they become client code to find out how well the parts fit together. 7 | * Discovery Tests II: they help me learn the new language and its API. I can formalize expectations (based on my Objective-C experiences) and try to replicate behavior in Swift. 8 | 9 | Discovery Tests are tests which aren't necessarily meant to stay. They take the place of `NSLog`ging values in production code. Instead of fiddling with the app itself, running it, and looking at values at breakpoints, you write expectations about implementation details and fiddle with the code until the tests pass. Afterwards, you get rid of the tests if you think they're not covering what should be tested. 10 | 11 | For example, in this project I try not to unit test every little detail. I want to add more functional tests which involve a whole graph of actual objects interacting with each other. So I'll have to consider removing tests which don't add a lot of benefit for the overhead of keeping them in sync with production code. 12 | 13 | ### Test-Driven Development on the Unit Level VS Functional Tests 14 | 15 | In the past I would've tried hard to develop my code driven by tests _on the unit level_. 16 | 17 | This poses an interesting challenge to the code's design, and it maybe affects the flexibility of the implementation. 18 | 19 | Let's say I have to add an algorithm `sum(input: [Int]) -> Int` which involves iterating over an array to sum up its elements. That's a straight-forward task none of us would need to think twice about. 20 | 21 | When doing hardcore Test-Driven Development, though, I'd to test the return value this algorithm according to the [Transformation Priority Premise][martin-tpp] like this: 22 | 23 | 1. Pass test for 1 element in: return a fixed value 24 | 2. Pass test for 1 different element in: resort to returning the only value in the array 25 | 3. For 2 elements in: if the element count is 2, return the sum of both elements 26 | 4. 10 elements in: loop over the array and return a sum of its elements 27 | 28 | Only in the last case I actually _have_ to resort to using a loop to get the easiest implementation to satisfy the expectations. I've truly developed the code guided by tests, avoiding to jump too far ahead at every point in time. 29 | 30 | The _Transformation Priority Premise_ totally makes sense to me: avoid premature optimization. Think simple. If you haven't read it, yet, I suggest you [do so right now.][martin-tpp] 31 | 32 | But what do you do with the earlier tests once you're at stage 4? Are all of them needed to verify that the algorithm works as expected? 33 | 34 | Keeping only the 4th test doesn't suffice: you could hard code the sum of the 10 elements from your test as the return value. In other words, you fall back to the "return constant" step. What if you pass in a different array? Wrong result! You need more than one test at any given point in time to verify the algorithm keeps computing the values like it did before. 35 | 36 | Transferring this insight to not so trivial code poses the following challenge: find out which tests were necessary during _discovery_ and ditch these. Keep the rest. Too many tests can actually hurt your application because they make it harder to do changes. Testing only _The Right Thing_ is hard. Maybe there's no guiding principle at all. At least I haven't found any. 37 | 38 | [martin-tpp]: http://blog.8thlight.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html 39 | 40 | 41 | ### Tests Should Prioritize Refactoring 42 | 43 | 44 | 45 | So I am puzzled. I thought that having more tests is always better, because you cover more and more edge cases with each test. But then again, too rigid testing makes code too hard to change. 46 | 47 | One necessary condition of successful [refactoring][] is that you have to be able to change the implementation without changing the behavior, thus breaking collaborators's expectations. On one hand, too explicit a test can be broken by refactoring code. So [tests can actually make refactoring harder.][objc2] On the other hand, refactoring code without tests is always a gamble: you can break behavior without noticing. 48 | 49 | > Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. 50 | > --- [Martin Fowler](http://www.refactoring.com/) 51 | 52 | It's hard for me to find a balance. But since I [publish the code of this project][bookcode], maybe with the help of other developers all of us can learn something about weighing test coverage. I'd appreciate a good discussion on the topic. 53 | 54 | In short, my guideline is the following: 55 | 56 | * Use tests to find out how components work together, 57 | * verify expected behavior through tests, making sure to cover edge cases, 58 | * remove discovery tests, and 59 | * remove tests which make too many assumptions about the internals in favor for black-box testing to make refactoring easier. 60 | 61 | With the years, I tend to do more and more functional testing or integration testing and try not to be too anal about unit tests, or else I end up with at least two places for any change I want to make. 62 | 63 | [objc2]: http://www.objc.io/issue-15/bad-testing-practices.html 64 | [refactoring]: http://c2.com/cgi/wiki?WhatIsRefactoring 65 | [bookcode]: https://github.com/CleanCocoa/mac-appdev-code 66 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/100-swift core data tests.txt: -------------------------------------------------------------------------------- 1 | ## How to Load Core Data `NSManagedObject`s in Tests 2 | 3 | For the most part, I am going to develop the example application in a Test-Driven manner. I will write tests first and add the user interface components and necessary service functionality later. You may call these functional tests. 4 | 5 | To get started, though, I will focus on unit tests for both the Core Data objects and the user interface components. 6 | 7 | The Core Data managed objects are called `ManagedBox` and `ManagedItem`. They mostly have a title and the relationship to one another: 8 | 9 | ![Core Data model](images/20141206190120_coredata.png) 10 | 11 | I add class-level helper methods to these to make [creating new Core Data entities][objc1] a bit easier. It will work like this: 12 | 13 | {linenos=off} 14 | ManagedItem.insertManagedItem(anItemId, title: "the title", 15 | inManagedObjectContext: aManagedObjectContext) 16 | 17 | Now I want to add tests to verify this helper method actually adds an entity to the context. Since the method is trivially simple, the tests will also show that Core Data-specific tests work, with their own in-memory scratchpad `NSManagedContext`. 18 | 19 | Turns out this was enough of a challenge for me as a beginner with Swift. 20 | 21 | Swift introduced access right management. We didn't have that with Objective-C. A test target for Swift source code wouldn't know about the program's source files unless you did one of two things up to Swift 2: 22 | 23 | 1. Declare classes and methods to test `public`, so they are visible outside the app source module. This may become somewhat tedious. 24 | 2. Add source files to the test target. This equals checking the "Tests" target in the Utilities browser's (Cmd+Alt+1) "Target Membership" list. 25 | 26 | Swift 2 introduces `@testable import` which enables the test target to have internal access to the host target. That's the way to go nowadays. 27 | 28 | ### Why Test Target Membership is a Bad Idea 29 | 30 | ![Managing source file target memberships](images/20141117095632_target-membership.png) 31 | 32 | Although it's easier to add sources to the test target at first, this may not be what you want to do. It is beneficial to think about what should be visible to the outside world and what is an internal detail only.[^2f0f329e3cbec9f624cc575b717291989c960595] The basic public/internal/private distinction (in Swift 3, it's actually: open, public, internal, fileprivate, or private) enforces object boundaries. A private method _cannot_ be called by another object; thus you _will not_ call it, ever. If it's accessible from the outside, you may be tempted to use it some day. Or your co-workers and team mates do. Because they don't know that they're not supposed to -- or they know but don't care. Enforcing sensible access rules helps to prevent misuse. 33 | 34 | 35 | Unit testing becomes harder for Core Data `NSManagedObject` subclasses this way, though. Adding these to the test target will make the tests compile. Upon test execution, though, you will trigger an [invisible breakpoint in bytecode][cdbp]. 36 | 37 | According to some folks, it may help to simply add `@objc(YourManagedObjectSubclass)`. It didn't in my case, though. I also had to make its interface public and remove it from the test target. So I end up with both the Objective-C bridge of `NSManagedObject` subclasses and public interfaces for all the stuff which shall be tested. 38 | 39 | ### Public Access 40 | 41 | Instead of `@testable import`, you could just import the app target like you'd import a library and resort to testing modules with public access. For the initial Swift 1.2 version and the Swift 2 update, I resorted to making code under test public. Because it _should_ not be a problem. If it was, that'd be an indicator of testing the wrong things. We can rationalize it this way: tests are in a different bundle, and tests should work with the public API only. 42 | 43 | As soon as you crave for testing private methods without making them public, you're having a dependency waiting to be extracted. Can you make a new type of this and have the current object under test delegate control and information to that new type instead? 44 | 45 | Recall the dependency injection example from above where I passed a `JSONSerializer` to the `jsonify` method: 46 | 47 | {linenos=off} 48 | func jsonify(serializer: JSONSerializer) -> JSON { 49 | serializer.add(self.title) 50 | serializer.add(self.items.map { $0.title }) 51 | return serializer.json() 52 | } 53 | 54 | I could probably create a valid JSON string on the fly in that method, but testing it would be harder for different cases. Also, I'd end up duplicating tests for different objects that can be "jsonified". With the `JSONSerializer` object in place, I can verify the serializer is set up properly and that the `add` method is called with the right parameters. The actual transformation into JSON is tested elsewhere; `JSONSerializer` clients can rely on it to work as advertised. 55 | 56 | I> **Rationale for Swift 2 and 3** 57 | I> 58 | I> Swift 2.0 introduces the `@testable` annotation: 59 | I> 60 | I> {linenos=off} 61 | I> @testable import YourAppTargetName 62 | I> 63 | I> Test targets will have "internal" access to the module under test. So you don't have to make everything you want to test public. 64 | I> 65 | I> Instead, make those things public which are potentially interesting to 3rd party client code. For apps, that's probably not much. For frameworks, this is a totally different matter. In the course of this book, we'll mark stuff private (or fileprivate) when appropriate, but won't worry much about what is public and what isn't, because we're not writing a library. 66 | I> 67 | 68 | [cdbp]: http://stackoverflow.com/questions/24841856/swift-breakpoint-in-coredata-library 69 | [objc1]: http://www.objc.io/issue-4/core-data-models-and-model-objects.html 70 | 71 | [^2f0f329e3cbec9f624cc575b717291989c960595]: See [commit 2f0f329](https://github.com/CleanCocoa/mac-appdev-code/commit/2f0f329e3cbec9f624cc575b717291989c960595) 72 | -------------------------------------------------------------------------------- /manuscript/part0-intro/300-motivation.txt: -------------------------------------------------------------------------------- 1 | {pagebreak} 2 | 3 | ## Motivation to Write This Book 4 | 5 | [I develop apps](http://christiantietze.de). My first big Mac application was the Word Counter. Back in 2014, I began to sell it. To actually make money from my own Mac software. And that made me nervous -- because what if I introduce tons of bugs and can't fix them? How do I write high-quality code so I can maintain the app for the next years? These questions drove my initial research that led to the field of software architecture. 6 | 7 | For an update in late 2014, I experimented with Core Data to store a new kind of data. That became quite a challenge: using Core Data the way of the documentation and sample codes results in a mess. Core Data gets intermingled everywhere. This little book and the accompanying project document my explorations and a possible solution to decouple Core Data from the rest of the app. 8 | 9 | ### Adding a New Feature to the Word Counter 10 | 11 | Rewind to fall 2014: I want to add a new feature to the Word Counter. 12 | 13 | Until now, the Word Counter observed key presses and counted words written per app. This is indented to show your overall _productivity_. But it can't show _progress_, another essential metric. The ability to monitor files to track project progress was next on the roadmap. 14 | 15 | That feature brings a lot of design changes along: 16 | 17 | * Introduce `Project`s and associate `Path`s with them. The domain has to know how to track progress for this new kind of data. 18 | * Add background-queue file monitoring capabilities. 19 | * Design new user interface components so the user can manage projects and tracked files' paths. 20 | * Store daily `PathRecord`s to keep track of file changes over a long period of time. Without records, no history. Without history, no analysis. 21 | * Make the existing productivity history view aware of `Project` progress. 22 | 23 | For the simple productivity metrics, I store application-specific daily records in `.plist` files. They are easy to set up and get started. But they don't scale very well. Each save results in a full replacement of the file. After 2 years of usages, for example, my record file clocks in at 3 MB. Someone who tracks even more apps and thus increases record history boilerplate will probably have a much bigger file. The hardware will suffer. 24 | 25 | In the long run, I'd transition to SQLite or Core Data. The file monitoring module I am about to develop back in 2014 can use its own isolated persistence mechanism. I decide to use Core Data because it's so convenient to use. 26 | 27 | Isolating the file monitoring module is one thing. To develop the module with proper internal separation of concerns and to design objects is another. Core Data easily entangles your whole application if you pick the path of convenience. That's usually not a path that scales well once you diverge from the standard way once. In this book, I write about my experience with another path, the path of pushing Core Data to the module or app boundaries and keep the innermost core clean and under control. 28 | 29 | 30 | ### Challenges Untangling the Convenient Mess 31 | 32 | Back in 2014, I didn't have any experience displaying nested data using `NSOutlineView`s. I didn't have a lot of experience with Core Data. All in all, most of the design decisions and their requirements were new to me. 33 | 34 | Teaching myself some AppKit, I fiddled around with providing data to `NSOutlineView` via `NSTreeController`. Cocoa Bindings are super useful, but they're not transparent. When it works, it works; but when it doesn't, it's hard to know why. Getting your hands dirty and acquiring hands-on knowledge is important, though, to get a feeling for the framework. Declarative or book knowledge will only get you so far. I managed to get a few use cases and basic interactions right, like appending projects to the list and adding paths to the projects. So an interface prototype without actual function wasn't too hard to figure out. 35 | 36 | Now add Core Data to the equation. 37 | 38 | It's super convenient to use Core Data because the framework takes care of _a lot_. If you want to manipulate persistent objects directly from your interface, you can ever wire `NSObjectController` subclasses like the above-mentioned `NSTreeController` to Core Data entities and skip _all of the object creation boilerplate code._ An `NSTreeController` can take care of displaying nested data in an `NSOutlineView`, adding items relative to the user's current selection for example. 39 | 40 | If you adhere to the limitations of an `NSTreeController`, that is. It is designed to operate on one type of (Core Data) entity. Unfortunately, users of the Word Counter will edit two kinds of objects, not one: `Path`s may only be nested below `Project`s, which in turn are strict root level objects. I have to enforce these rules myself, and I have to add objects to the tree from code on my own. 41 | 42 | To tie Core Data entities (or `NSManagedObject` subclasses) to the user interface is so convenient because it skips all of the layers in between. No view controller, no custom domain objects. **This means, in turn, to couple the user interface to the data representation.** In other words, the user-facing windows are directly dependent on the database. This may be a clever short cut, but this might also be the reason why your app is getting hard to change and hard to test. 43 | 44 | The data representation should stay an implementation detail. I may want to switch from Core Data to a SQLite library later on, just like I'm going to migrate my `.plist` files to something else in the future. The app has to be partitioned _at least_ into "storing data" and "all the rest" to switch the "storing data" part. To blur all the boundaries is commonly referred to as _Big Ball of Mud_ architecture (or rather "non-architecture"). Partitioning of the app into sub-modules improves clarity. And the convenient use of Core Data interferes with this objective. 45 | 46 | In short, using Core Data in the most convenient way violates a lot of the principles I learned and loved to adhere to. Principles that helped me keep the Word Counter code base clean and maintainable. Principles I'm going to show you through the course of this book. 47 | 48 | The basic architecture I want to advocate is sometimes called "[Clean Architecture][cleanar]", sometimes "[Hexagonal][hexagonal]". There are differences between these two, but I won't be academic about this. This little book will present you my interpretation on these things first and point you into the right direction to find out more afterwards. 49 | 50 | [cleanar]: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html 51 | [hexagonal]: http://alistair.cockburn.us/Hexagonal+architecture 52 | -------------------------------------------------------------------------------- /manuscript/part4-coredata/200-abstracting away core data.txt: -------------------------------------------------------------------------------- 1 | ## Abstract from Core Data 2 | 3 | Protocols are a great way to hide implementation details. In it's basic form, a protocol is just an interface declaration. Much like the ones Java folks use. 4 | 5 | In Swift, as opposed to Objective-C, protocols are used in very interesting ways throughout the standard library. One of these interesting ways has to do with generics. Luckily, that's nothing we have to deal with much here, because generics add another layer of complexity to our reasoning. Then Swift 2 brought protocol extensions, which are a bit like abstract classes in Java: you cannot instantiate protocols, but you can provide fallback behavior.[^prot] 6 | 7 | [^prot]: In Swift, it matters which explicit type a variable has to determine if the method from the protocol extension is used or an overriding implementation from a class or struct. If the type of a variable is the protocol itself, the method implementation from the protocol extension will be used no matter what the concrete type does. That can be a cool feature, or outright confusing. 8 | 9 | Keeping it simple, we can leverage protocols to define which part of the model types should be public API. 10 | 11 | The functionality we created in the first three parts of the book can be distilled to the following protocols: 12 | 13 | protocol ItemType: class { 14 | var title: String { get } 15 | var itemId: ItemId { get } 16 | var box: BoxType { get } 17 | 18 | func changeTitle(title: String) 19 | } 20 | 21 | protocol BoxType: class { 22 | var boxId: BoxId { get } 23 | var title: String { get } 24 | var items: [ItemType] { get } 25 | 26 | func addItemWithId(itemId: ItemId, title: String) 27 | func item(itemId itemId: ItemId) -> Item? 28 | func removeItem(itemId itemId: ItemId) 29 | 30 | func changeTitle(title: String) 31 | func changeItemTitle(itemId itemId: ItemId, title: String) 32 | } 33 | 34 | That's the public API of what's left of the Domain Model. 35 | 36 | I still prefer to avoid mutable properties and stick to `changeTitle(_:)` to convey that this is a command. 37 | 38 | The existing Core Data implementation actually has everything we will need to expose: there's an `NSSet` of associated items on `Box` which can be transformed to an array easily. We can hide all the rest of Core Data functionality, including the fact that both `Box` and `Item` are based on `NSManagedObject` and `NSObject` using these protocols. 39 | 40 | The result is a code base which still reads as if you had a real Domain Model without external dependencies. 41 | 42 | Clients of the model objects should not care about Core Data specific features. Since we started _without_ putting Core Data at the center of our architecture, the model objects were well isolated. Most of the application was changed quickly in the last step already. Good isolation usually entails easy to change code. 43 | 44 | Without the patterns and principles which guided implementing our architecture, the code would have looked a lot worse. Conversely, changes would have taken more time. Repositories are no part of the usual Cocoa programmer's toolbelt. But they helped in production code, and they help us programmers perform changes. 45 | 46 | The actual transition to protocol-based model objects is simple. 47 | 48 | ### Implementing the Protocols 49 | 50 | Since most properties of the managed objects match the protocol requirements already, making `Item` adopt `ItemType` requires only a few lines of code: 51 | 52 | extension Item: ItemType { 53 | public var itemId: ItemId { 54 | return ItemId(uniqueId.int64Value) 55 | } 56 | 57 | public var box: BoxType { 58 | return managedBox as BoxType 59 | } 60 | } 61 | 62 | `Box` takes a bit more effort, because it actually exposes behavior. But most of it was introduces during the conversion of the project to use Core Data objects already. 63 | 64 | Since an array of `ItemType` is available as part of the protocol-defined API, some methods are now easier to implement and thus easier to read: 65 | 66 | extension Box: BoxType { 67 | public var boxId: BoxId { return BoxId(uniqueId.int64Value) } 68 | 69 | public var items: [ItemType] { 70 | return managedItems.allObjects.map { $0 as! ItemType } 71 | } 72 | 73 | public func item(itemId itemId: ItemId) -> ItemType? { 74 | return items.filter { $0.itemId == itemId }.first 75 | } 76 | 77 | public func removeItem(itemId itemId: ItemId) { 78 | guard let item = self.item(itemId: itemId) 79 | else { return } 80 | 81 | let existingItems = self.mutableSetValueForKey("managedItems") 82 | existingItems.removeObject(item) 83 | } 84 | // ... 85 | } 86 | 87 | The cost of using protocols is next to none, the benefit for clarity of code internally is huge. When code depends on these protocols instead of the concrete `NSManagedObject` subclasses, we can still hide the fact that Core Data in involved from most parts of the app. That makes using the objects easier. When the interface is limited to a few methods an properties, you know what you are supposed to do. When you get all of the power of `NSObject` and `NSManagedObject`, this power quickly turns into a liability. Object boundaries can be violated easily with Key-Value-Coding, and the framework might enforce access levels we'd rather not make public. Using a protocol as an abstraction helps to keep boundaries in tact and reveal only the parts you want client code to use. 88 | 89 | ### How to Cast Collections Down to the Protocol 90 | 91 | You'll get runtime errors when you try to cast `[AnyObject]` arrays bridged from Objective-C `NSArray`s into arrays of a concrete type: 92 | 93 | {linenos=off} 94 | public var items: [ItemType] { 95 | return managedItems.allObjects as! [ItemType] 96 | // fatal error: array cannot be bridged from Objective-C 97 | } 98 | 99 | Instead, you have to map each element: 100 | 101 | {linenos=off} 102 | public var items: [ItemType] { 103 | return managedItems.allObjects.map { $0 as! ItemType } 104 | } 105 | 106 | An array is actually a generic `Array` in Swift; casting from `Array` to `Array` doesn't make sense to the compiler. The behavior I had expected initially would be to cast each element to `ItemType`, resulting in the array of the desired type. But that's not how things roll, so we have to cast each element using `map` instead and get the desired array as a result of this operation. 107 | -------------------------------------------------------------------------------- /manuscript/part2-process/400-viper.txt: -------------------------------------------------------------------------------- 1 | ### VIPER & Naming conventions {#viper} 2 | 3 | ![VIPER, illustrated. Picture credit: Jeff Gilbert, Conrad Stoll, and Lin Zagorski of Mutual Mobile. Used with permission.](images/201503130948_viper.jpg) 4 | 5 | Renaming the **Application Service**s to something more fitting, I end up with: 6 | 7 | * `ManageBoxesAndItems` as the use case, 8 | * `DisplayBoxesAndItems` as its _presenter_, and 9 | * `HandleBoxAndItemModifications` as its _event handler_ (the former `BoxAndItemService`). 10 | 11 | Only `ManageBoxesAndItems` has to be public. The rest is an implementation detail and, in fact, a refactoring. Thanks to the functional tests in place, it's easy to verify the behavior while fleshing out the new parts. 12 | 13 | I named the collaborating services according to the [VIPER architecture][viper]: a **Presenter** updates the view on changes, while an **Event Handler** is delegate to user interaction. The use case object `ManageBoxesAndItems` replaces VIPER's notion of a **Wireframe** which suits iOS apps and their view transitions better than multi-windowed Mac apps. 14 | 15 | ![VIPER components in practice](images/20150723132836_viper-arch.png) 16 | 17 | Quoting [Jeff Gilbert and Conrad Stoll][viper] here, the main parts of VIPER are: 18 | 19 | > * _View:_ displays what it is told to by the Presenter and relays user input back to the Presenter. 20 | > * _Interactor:_ contains the business logic as specified by a use case. 21 | > * _Presenter:_ contains view logic for preparing content for display (as received from the Interactor) and for reacting to user inputs (by requesting new data from the Interactor). 22 | > * _Entity:_ contains basic model objects used by the Interactor. 23 | > * _Routing:_ contains navigation logic for describing which screens are shown in which order. 24 | 25 | So `ManageBoxesAndItems` is essentially the Interactor, `DisplayBoxesAndItems` the Presenter with its partner `HandleBoxAndItemModifications` being an adapter to the port its view defines. `Box` and `Item`, finally, are the entities. There's no navigation, thus no routing. 26 | 27 | [viper]: http://www.objc.io/issue-13/viper.html 28 | 29 | ### Consuming `Box`es and `Item`s instead of obtaining IDs 30 | 31 | The old methods which were both _command_ and _query_ are gone. Instead, there is an event handler which receives commands from the user interface or view, and there's a presenter which updates the view according to changes in the domain. 32 | 33 | The view _consumes_ changes. It sounds weird a bit, but I didn't come up with a better name, yet.[^change] 34 | 35 | [^change]: Remember you can propose changes to [the book's manuscript][bookcode] on GitHub! 36 | 37 | 38 | Consuming data for a `BoxNode` is pretty easy because the nodes are just appended to the tree: 39 | 40 | public func consume(boxData: BoxData) { 41 | let boxNode = self.boxNode(boxData) 42 | let indexPath = IndexPath(index: nodeCount()) 43 | itemsController.insertObject(boxNode, 44 | atArrangedObjectIndexPath: indexPath) 45 | orderTree() 46 | } 47 | 48 | {pagebreak} 49 | 50 | Consuming data for `ItemNodes` is a bit more intricate, but still not too complicated: 51 | 52 | public func consume(itemData: ItemData) { 53 | guard let boxId = itemData.boxId, 54 | boxNode = existingBoxNode(boxId) 55 | else { return } 56 | 57 | let itemId = itemData.itemId 58 | let itemNode = self.itemNode(itemData) 59 | 60 | boxNode.addItemNode(itemNode) 61 | orderTree() 62 | } 63 | 64 | Thanks to `NSTreeController`, `addItemNode`, which is appending the argument to the receiver's `children` property, results in automagical updates of the view. 65 | 66 | Walking the first level of `NSTreeNodes` to find the `BoxNode` with a specific ID sounds like a waste of CPU cycles to me, but it's not a problem at all, especially not with smaller lists. No need to think about optimizing anything here just yet.[^8abe3c0a0eee951e70a0375e96d2775cc26a3597] 67 | 68 | 72 | 73 | [^8abe3c0a0eee951e70a0375e96d2775cc26a3597]: See [commits 8abe3c0](https://github.com/CleanCocoa/mac-appdev-code/commit/8abe3c0a0eee951e70a0375e96d2775cc26a3597) and [ba050fb](https://github.com/CleanCocoa/mac-appdev-code/commit/ba050fbfeb2adbb6656c0b0477b62e2e5705f5b8) 74 | 75 | ### Separation Helps Dealing with Concurrency 76 | 77 | Separating event handling from updating the view can have nice benefits. Imagine there's a background thread syncing the database to a server: you can display changes with the existing setup just like that. 78 | 79 | To test how good it really is, I delay view updates a bit using `asyncAfter` (formerly `dispatch_after`) with the following global helper function: 80 | 81 | {title="Delay helper in Utilites"} 82 | func delay(_ delay: Double, closure: @escaping () -> Void) { 83 | let deadline = DispatchTime.now() + .milliseconds(Int(delay * 1000)) 84 | DispatchQueue.main.asyncAfter( 85 | deadline: deadline, execute: closure) 86 | } 87 | 88 | The delay can be inserted at various places. Put it into a service to simulate long-running operations, for example. Now if I press "New Box" multiple times, it inserts the boxes one after another with a delay. 89 | 90 | At the moment, there's no way to rename `Box` or `Item` in the domain and push changes to the view. Currently, the view optimistically assumes its updates will persist and displays the change on its own terms, without a round-trip to the domain (or persistence). 91 | 92 | With a delay of about 10 seconds, I can even rename existing nodes before new ones come in without any conflicts. Try that with an omnipotent, tightly coupled, always blocking read--write view controller. 93 | 94 | Keep in mind, though, that this needn't be a sign of high quality. Maybe for your app, a closer coupling of view and model data at all times is important. In most cases, this temporal decoupling is a useful feat, though. 95 | 96 | I think the view should at least optimistically add view components instantaneously even though the persistence takes some time. Instant user feedback is important to make the app appear responsive. Long delays confuse the user: did the action work or did it not? An optimistic approach matters more when there are round-trips to a server which can take long. In web applications, this is a must to ensure low perceived latency. Mac applications don't suffer from low responsiveness most of the time; saving simple data sets to Core Data isn't a problem. If we put a delay in `CoreDataBoxRepository`, the user interface should not be lagging behind: 97 | 98 | {linenos=off, title="Delay saving a new Box"} 99 | open func addBox(_ box: Box) { 100 | delay(5) { 101 | ManagedBox.insertManagedBox(box.boxId, 102 | title: box.title, 103 | inManagedObjectContext: self.managedObjectContext) 104 | } 105 | } 106 | 107 | You'll see what I think should happen when asynchronous persistence fails even though the user interface optimistically shows a value [in the upcoming part](#errorevents). 108 | 109 | Splitting concerns is good most of the time, but it's not always easily done. 110 | 111 | When I added a real user interface to the Word Counter, I used a separation similar to Wireframe/Presenter/Event Handler as **Application Services**, only the Event Handler was really an Interactor with close affinity to the domain, pulling in changes from running **Domain Services**. I wasn't ready to do experiments with the architecture for quite a while. Now I feel confident that a strong domain with **Domain Events** can yield better results. The _pull_-oriented Interactor can fetch a snapshot of the data, but if it changes in the background, the user-facing values are getting stale quickly. A _push_-oriented approach, on the other hand, easily remedies such problems. 112 | 113 | So up next is the part most interesting to _Domain-Driven Design_: fleshing out the Domain some more and adding behavior next to the Entities. 114 | -------------------------------------------------------------------------------- /manuscript/part2-process/300-ports adapters.txt: -------------------------------------------------------------------------------- 1 | {pagebreak} 2 | 3 | ## Ports and Adapters 4 | 5 | Recall that the basic idea of this architectural style is this: separate queries from commands and isolate layers through ports and adapters. _Ports_ are interfaces (protocols) declared in one layer, while _Adapters_ are classes from other layers which satisfy a Port's specification: 6 | 7 | ![Ports and Adapters](images/20141204100034_ports-adapters.png) 8 | 9 | Instead of one adapter knowing about its collaborator on the other side of the boundary, each adapter only knows a protocol from its module. That marks a dependency. The actual implementation of that protocol is _injected._ This move is sometimes called "Dependency Inversion," because you don't go the naive route and depend on a component from another module but instead depend on a protocol from your own module that others satisfy. The "inversion," then, is the inversion of arrow directions. 10 | 11 | This is nothing new to the code, actually. For example, `BoxRepository` is a port of the domain to which `CoreDataBoxRepository` from infrastructure is an adapter. Similarly, the `HandlesItemListEvents` protocol from the user interface layer is implemented by an application service. 12 | 13 | I want to refactor `HandlesItemListEvents` to pay more attention to command--query separation. 14 | 15 | 16 | ### Concerns With the Naive Approach {#refactoring-to-commands} 17 | 18 | Have a look at the event handling protocol again: 19 | 20 | {linenos=off} 21 | provisionNewBoxId() -> BoxId 22 | 23 | In fact, this method is kind of a mix between typical data source and delegate method. It's intent is a command, suitable for user interaction events. But it's also meant to return an object like a factory or data source does. 24 | 25 | It should be rephrased as such: 26 | 27 | {linenos=off} 28 | provisionBox() 29 | newBoxId() -> BoxId 30 | 31 | The two processes can't be split up in the **Application Service**, though. With the current expectations in place, `newBoxId()` would have to return the last ID the service had provisioned. This way, everything will fall apart too easily once `provisionBoxId()` is called twice. Just think about concurrency. The contract to call both methods in succession only can't be enforced, so better not rely on it. 32 | 33 | Another alternative is to model it as such: 34 | 35 | {linenos=off} 36 | newBoxId() -> BoxId 37 | provisionBox(_: BoxId) 38 | 39 | The service will be an adapter to both domain and infrastructure this way. It'd work, but it's not what I aim for. 40 | 41 | To model the expectation of "provision first, obtain ID second" more explicitly, one could introduce a callback to write the ID to, like so: 42 | 43 | {linenos=off} 44 | provisionBox(andReportBoxId: (BoxId) -> Void) 45 | 46 | I don't like how that reads, though. And since there's just a single window present, we can formalize this as another protocol from the point of view of `BoxAndItemService`. 47 | 48 | My very first take: 49 | 50 | {title="Introduce consumer protocol as output port"} 51 | protocol ConsumesBoxId: class { 52 | func consume(boxId: BoxId) 53 | } 54 | 55 | class BoxAndItemService: HandlesItemListEvents { 56 | // Output port: 57 | var boxIdConsumer: ConsumesBoxId? 58 | 59 | func provisionBox() { 60 | let repository = ServiceLocator.boxRepository() 61 | let boxId = repository.nextId() 62 | storeNewBox(withId: boxId, into: repository) 63 | reportToConsumer(boxId: boxId) 64 | } 65 | 66 | fileprivate func storeNewBox(withId boxId: BoxId, 67 | into repository: BoxRepository) { 68 | 69 | let box = Box(boxId: boxId, title: "New Box") 70 | repository.addBox(box) 71 | } 72 | 73 | fileprivate func reportToConsumer(boxId: BoxId) { 74 | // Thanks to optional chaining, a non-existing boxIdConsumer 75 | // will simply do nothing here. 76 | boxIdConsumer?.consume(boxId: boxId) 77 | } 78 | } 79 | 80 | Instead of _querying_ `BoxAndItemService` for an ID, the view controller can now _command_ it to provision a new `Box`. The service won't do anything else. Clicking the button will add a `Box` as it says on the label, but it won't change the view. The design of these components is flexible and the service doesn't know the consumer. It is not tied to a view component. You could say that it's just a coincidence the `ItemViewController` in return receives the _command_ to `consume()` a `BoxId`. This, in turn, will trigger adding a new node with the appropriate `BoxId` to the outline view. 81 | 82 | 83 | ### Using Domain Services and Domain Events 84 | 85 | What `reportToConsumer(boxId:)` does equals the intent of the well-known _observer pattern_. In Cocoa, we usually send notifications. This is more like calling a delegate because it's a 1:1 relationship instead of the many-to-one observer pattern relationship: 86 | 87 | {linenos=off} 88 | func reportToConsumer(box: Box) { 89 | boxConsumer?.consume(box) 90 | } 91 | 92 | But I notice the **Application Service** `BoxAndItemService` is now doing these things: 93 | 94 | * it sets up the aggregate `Box` 95 | * it adds the aggregate instance to its repository 96 | * it notifies interested parties (limited to 1) of additions 97 | 98 | Essentially, that's the job of a **Domain Service**. 99 | 100 | The domain has a few data containers, but no means to create **Aggregate**s or manipulate data. The application layer, being client to the domain, shouldn't replace domain logic. Posting "Box was created" events is the domain's responsibility. 101 | 102 | Using `NotificationCenter` as a domain event publisher, `BoxAndItemService` loses part of its concerns in favor of a Domain Service, `ProvisioningService`: 103 | 104 | {title="Refactoring business logic into a dedicated Domain Service"} 105 | open class ProvisioningService { 106 | let repository: BoxRepository 107 | 108 | var eventPublisher: NotificationCenter { 109 | return DomainEventPublisher.defaultCenter() 110 | } 111 | 112 | public init(repository: BoxRepository) { 113 | self.repository = repository 114 | } 115 | 116 | open func provisionBox() { 117 | let boxId = repository.nextId() 118 | let box = Box(boxId: boxId, title: "New Box") 119 | 120 | repository.addBox(box) 121 | 122 | eventPublisher.post( 123 | name: Events.boxProvisioned, 124 | object: self, 125 | userInfo: ["boxId" : boxId.identifier]) 126 | } 127 | 128 | open func provisionItem(inBox box: Box) { 129 | let itemId = repository.nextItemId() 130 | let item = Item(itemId: itemId, title: "New Item") 131 | 132 | box.addItem(item) 133 | 134 | let userInfo = [ 135 | "boxId" : box.boxId, 136 | "itemId" : itemId 137 | ] 138 | eventPublisher.post( 139 | name: Events.boxItemProvisioned, 140 | object: self, 141 | userInfo: userInfo) 142 | } 143 | 144 | // (Mis)using enum as a namespace for notifications: 145 | enum Events { 146 | static let boxProvisioned = 147 | Notification.Name(rawValue: "Box Provisioned") 148 | static let boxItemProvisioned = 149 | Notification.Name(rawValue: "Box Item Provisioned") 150 | } 151 | } 152 | 153 | I'm not all that keen about the way `NotificationCenter`s work. Dealing with the `userInfo` parameter is error-prone; both during consumption and creation you can have a typo in a key and end up with a runtime error or unexpected behavior. 154 | 155 | I think `provisionItem` doesn't read too well, but it's not the worst method in human history, either. But getting the data out is getting bad: 156 | 157 | {linenos=off} 158 | let boxInfo = notification.userInfo?["boxId"] as! NSNumber 159 | let boxId = BoxId(fromNumber: boxInfo) 160 | 161 | It's hard to read and complicated when compared to, say: 162 | 163 | {linenos=off} 164 | let boxId = boxCreatedEvent.boxId 165 | 166 | To introduce another layer of abstraction is a good idea, especially since Swift's structs make it really easy to create **Domain Event**s. It will improve the code, although you'll have to weigh the additional cost of developing and testing this very layer of abstraction. For the sake of this sample application, I prefer not to over-complicate things even more and stick to plain old notifications for now. 167 | 168 | To replace the command--query-mixing methods from before, there needs to be an event handler which subscribes to Domain Events in the application layer and displays the additions in the view. 169 | 170 | Before I expand the Domain with these event types, though, we'll have a look at another refactoring in the Application layer. 171 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/300-view controller basics.txt: -------------------------------------------------------------------------------- 1 | ## Preparing the UI: Create View Controllers and Test Their Setup 2 | 3 | We'll ignore Storyboards for the Mac SDK for now when creating the view. This example project will have just one window and no transitions, so there's no benefit in using Storyboards. Storyboards suggest different kinds of view transitions and event handling, which would only distract from what we're really up to. 4 | 5 | I adopted the following practice for creating views: 6 | 7 | * Use a custom `NSWindowController` subclass for each window or panel and give it its own Nib file. 8 | * Use a designated `NSViewController` subclass for each logical unit of the view. In the case of the Box-and-Item-management view, there is only one such component. 9 | * When the unit is used only once, wire its outlets up from within the window's Nib. 10 | * When the unit is re-used, put it into its own Nib file and connect the outlets there instead of the window's Nib. 11 | 12 | Not every button needs its own controller, but buttons modifying a list belong to the list's view controller. 13 | 14 | So we take the following steps: 15 | 16 | * Add `ItemManagementWindowController` and the accompanying Nib file. 17 | * Add an `NSOutlineView` to the window and bind it to a `ItemViewController` which worries about data handling exclusively. 18 | * Add an `NSTreeController` to the Nib to manage the actual `NSOutlineView` data. It's strongly referenced by the `ItemViewController`. 19 | 20 | 21 | {pagebreak} 22 | 23 | ### Remove the Burden of UI from AppDelegate 24 | 25 | The Mac Cocoa project template used `AppDelegate` as the owner of the main window. I tend to put `AppDelegate` into Infrastructure. The user interface belongs elsewhere, so factoring the main window out into its own controller will both slim down `AppDelegate` and make the overall app design more supple instantly. 26 | 27 | Thus, remove every notion of `NSWindow` from the `AppDelegate` and show `ItemManagementWindowController`'s window from within `applicationDidFinishLaunching()`. That suffices to show the user interface.[^5090735c386896e031551dc9e0c47a3e5d351d83] 28 | 29 | 30 | I don't trust my ability to use Interface Builder a lot, and I assume I will get Cocoa Bindings wrong from time to time. Thus, I add regression tests to show I have set up everything as intended: 31 | 32 | 33 | // ItemViewControllerTests.swift 34 | func testItemsController_IsConnected() { 35 | XCTAssertNotNil(viewController.itemsController) 36 | } 37 | 38 | func testItemsController_CocoaBindings() { 39 | let controller = viewController.itemsController 40 | let outlineView = viewController.outlineView 41 | let titleCol = outlineView.tableColumnWithIdentifier(kTitleColumnName) 42 | let countCol = outlineView.tableColumnWithIdentifier(kCountColumnName) 43 | 44 | XCTAssertTrue(hasBinding(controller, binding: NSSortDescriptorsBinding, 45 | to: viewController, throughKeyPath: "self.itemsSortDescriptors")) 46 | XCTAssertTrue(hasBinding(outlineView, binding: NSContentBinding, 47 | to: controller, throughKeyPath: "arrangedObjects")) 48 | 49 | XCTAssertTrue(hasBinding(titleCol!, binding: NSValueBinding, 50 | to: controller, throughKeyPath: "arrangedObjects.title")) 51 | XCTAssertTrue(hasBinding(countCol!, binding: NSValueBinding, 52 | to: controller, throughKeyPath: "arrangedObjects.count")) 53 | } 54 | 55 | 56 | 57 | {#x20141124163838} 58 | I hunted for a bug for a while only to find the Cocoa Binding from `NSOutlineView` to `NSTreeController` is in place _although I don't set it explicitly_. If I _do_, the behavior goes all nuts. It seems that the column bindings make up for the rest. I have no clue how to verify this kind of automated set-up in the Nib without explicit bindings slipping by. Parsing the Nib XML is out of question.[^dc824da1016025abd8be61fafb560995527dccfb] 59 | 60 | 61 | [^dc824da1016025abd8be61fafb560995527dccfb]: See [commit dc824da](https://github.com/CleanCocoa/mac-appdev-code/commit/dc824da1016025abd8be61fafb560995527dccfb) 62 | [^5090735c386896e031551dc9e0c47a3e5d351d83]: See [commit 5090735](https://github.com/CleanCocoa/mac-appdev-code/commit/5090735c386896e031551dc9e0c47a3e5d351d83) 63 | 64 | 65 | ### Create `XCTestCase` Extensions with Test Helpers 66 | 67 | The function `hasBinding(_,binding:,to:,throughKeyPath:)` I used in the test case above is a little helper to inspect the underlying bindings dictionary from tests in a readable fashion: 68 | 69 | extension XCTestCase { 70 | func hasBinding( 71 | _ object: NSObject, 72 | binding: String, 73 | to boundObject: NSObject, 74 | throughKeyPath keyPath: String) -> Bool { 75 | 76 | guard let info = object.infoForBinding(binding) 77 | else { return false } 78 | 79 | let observedObject = info[NSObservedObjectKey] as! NSObject 80 | let observedKeyPath = info[NSObservedKeyPathKey] as! String 81 | 82 | return observedObject.isEqual(boundObject) 83 | && observedKeyPath == keyPath 84 | } 85 | 86 | With the test in place, I can know that the basic setup is working. 87 | 88 | ### A Note on Splitting Concerns Into Objects and Creating so Many Files 89 | 90 | Was the sudden explosion of files worth it? Couldn't we've kept the AppDelegate to manage the window? 91 | 92 | I tend to react to questions like this with skepticism. Why worry about the amount of files? What's the underlying motive for such a question? Most of the time, it's a feeling of discomfort. It's usually folks who learned to cram everything into the `AppDelegate` or into view controllers who bring this up. It's a defense mechanism. 93 | 94 | To extract these things into different types and files may be a new approach for you, too. I have to ask you to follow me along this path for now and judge the end result for yourself. "Too many files" is a vanity metric. The file count will neither prove your app is well-factored and clean, nor that it's the opposite. It's meaningless. So don't get hung up on superficial stuff like this and focus on the real work: designing focused and maintainable objects. 95 | 96 | So why now? The app doesn't do a thing, yet, which means I only introduced overhead. That's right, but I know that I don't want to cram stuff into my `AppDelegate` later. At the moment, the additions are simple and cheaply made. There's no point in saving a couple of calories now only to spend more energy tearing out code later. Since leaving window management and view handlnig in the `AppDelegate` is out of question, this is a first move to adjust the overall design and architecture to my needs. 97 | 98 | Once we begin to send messages here and there, the split between objects will make a lot more sense. We're getting there and you'll see how I prove my point. 99 | 100 | ### Understanding View Controller Tests 101 | 102 | To test controls such as buttons, there's no need to simulate clicks in the tests, although that's probably the first thing you want to do. After you analyze the process of button click handling, you can see what should be tested instead. Action handling involves proper setup in the Nib from outlet to action method first. We can test that, but we don't need to verify what "wiring outlet to action" means; we should trust the AppKit framework to do its job. Testing that Nib wiring does what it advertises will not give us more confidence because we cannot control AppKit's internals. Verifying a button click thus turns into a two-step process: verify the button is wired to an action method, then verify that invoking this method does what you expect. The actual click is not our concern.[^c5c351b54e53a5d22b44c20e376bca521f451e54] 103 | 104 | {linenos=off} 105 | // Test that there's a button outlet 106 | func testAddItemButton_IsConnected() { 107 | XCTAssertNotNil(viewController.addItemButton) 108 | } 109 | 110 | // Test that it is connected to an action method 111 | func testAddItemButton_IsWiredToAction() { 112 | XCTAssertEqual(viewController.addItemButton.action, 113 | #selector(ItemViewController.addItem(_:))) 114 | } 115 | 116 | // Specify preconditions in "Initially" context 117 | func testInitially_TreeIsEmpty() { 118 | XCTAssertEqual(itemNodeCount(), 0, "start with empty tree") 119 | } 120 | 121 | // Invoke the button's action method to verify its behavior 122 | func testAddingItem_WithEmptyList_AddsItem() { 123 | viewController.addItem(self) 124 | 125 | XCTAssertEqual(itemNodeCount(), 1, "adds item to tree") 126 | } 127 | 128 | Finally, once the "add" button does what is is created to do, we know that the tests are configured correctly. 129 | 130 | 132 | 133 | [^c5c351b54e53a5d22b44c20e376bca521f451e54]: See [commit c5c351b](https://github.com/CleanCocoa/mac-appdev-code/commit/c5c351b54e53a5d22b44c20e376bca521f451e54) and [7648d60](https://github.com/CleanCocoa/mac-appdev-code/commit/7648d603c832c5d7cd41a041da5d3e4114f7c955) 134 | -------------------------------------------------------------------------------- /manuscript/part2-process/100-naive event handler.txt: -------------------------------------------------------------------------------- 1 | ## Naive Event Handler 2 | 3 | Somehow, the `BoxRepository` has to know about the user clicking a button, thus issuing the creation of a new `Box`. `BoxRepository` belongs to the domain, although the concrete `CoreDataBoxRepository` is part of infrastructure. It's part of infrastructure because the domain's clients shouldn't have knowledge about this implementation detail, even less so the domain. 4 | 5 | The missing glue is between user interface and the domain. That's the Application layer: the premier client to the domain. 6 | 7 | 8 | ### View Controllers Don't Have to Belong to the Application Layer 9 | 10 | Depending on how you create `NSViewController` subclasses, view controllers either belong to the application layer or to the user interface layer. I tend to write dumb view controllers which belong into the user interface layer, next to custom `NSView`s and Nib files. If view controllers become too clever, it's harder to add features or re-use user interface components. That's why I want to separate the UI from Core Data in the first place. In other words, I'm pushing view controllers into the UI layer, and the UI layer is pushed to the outermost shell of the application. 11 | 12 | Now when view controllers are too dumb to perform interesting actions, they have to delegate this behavior to other objects. View controllers will worry about view lifecycle events, like "X is shows" or "update Y with Z". They are a _facade_ to the real user interface components, taking care of the set-up, layout, and basic event handling. View controllers coordinate views (`NSView` or `UIView` subclasses) and form a more complex user interface component. They aggregate their views's basic events and take care of sending more meaningful events to interested parties. They delegate the real work in both directions: handling user events up one level, displaying data down one level. 13 | 14 | An event handler object in the Application layer is such a collaborator. It understands nothing about buttons and outlines. Instead, it answers questions like "How many `Item`s are there in this `Box`?" and takes care of events like "Create a new `Box`." 15 | 16 | Handling events in the application layer instead of the view controller introduces the option to phrase the events in a way suitable to the task. You can refer to the **Ubiquitous Language** of your project to describe these events in code: talk in terms of "create `Box`" instead of "`addBoxButton` clicked". 17 | 18 | ### Leverage the Delegate Pattern 19 | 20 | The delegate pattern is widespread in Apple's own framework. You implement a `NSTableViewDelegate` to handle `NSTableView`-specific events. You also need a `NSTableViewDataSource` to provide (initial) data to the table. The table _asks_ for this information. You don't have to shove it down its throat upon initialization. This way, the table can worry about performance issues like caching table cells and ask for parts of the data at your disposal only. 21 | 22 | ![Event Handler](images/20141125103816_components--event_handler.jpg) 23 | 24 | It's pretty easy to add this to the code there is already. 25 | 26 | {linenos=off} 27 | public protocol HandlesItemListEvents: class { 28 | func provisionNewBoxId() -> BoxId 29 | func provisionNewItemId(inBox boxId: BoxId) -> ItemId 30 | } 31 | 32 | I tend to phrase protocols this way because it makes the role clear and reads pretty well: 33 | 34 | {linenos=off} 35 | class Foo: HandlesItemListEvents { /* ... */ } 36 | 37 | This reads "The class 'Foo' handles item list events". Works for me, but may disgust you. This convention isn't mandatory, but please give it a chance for now. 38 | 39 | If the protocol name is clear, how do you name a proper **Application Service**? 40 | 41 | I favor good names. Back in Java-land, code was cluttered with `XYZService`s and `ABCManager`s. These nouns don't convey a lot of meaning. It requires knowledge of the **Ubiquitous Language** to come up with better names. Most of the really good names will be reserved for the Domain, though, and not pop up in the Application layer. 42 | 43 | For example, the Word Counter domain includes a `Recorder` and various `Bookkeeper`s. The former takes care of the various ways to track words (all words, words per application, words in project files) while the latter holds on to the actual data in memory, incrementing counters. Both are service objects. 44 | 45 | The application layer is always going to be pretty thin since it mostly consists of glue code, delegating to other objects that do the actual work. That's why I do not mind falling back to the `XYZService` naming convention for now, even though that's name's not giving away much information. Here's an example: 46 | 47 | public class BoxAndItemService: HandlesItemListEvents { 48 | public func provisionNewBoxId() -> BoxId { 49 | let repository = ServiceLocator.boxRepository() 50 | let boxId = repository.nextId() 51 | let box = Box(boxId: boxId, title: "New Box") 52 | 53 | repository.addBox(box) 54 | 55 | return boxId 56 | } 57 | 58 | // ... 59 | } 60 | 61 | The implementation of `provisionNewBoxId` is simple. Provisioning items belonging to boxes will be a bit more intricate. 62 | 63 | The functional test still fails because the `BoxAndItemService` is never actually loaded. Whichever object is responsible for creating the window must introduce this event handler to the view controller. Currently, that's the job of `AppDelegate`. 64 | 65 | ### Wire together existing components 66 | 67 | `AppDelegate` sets up the view controller like this: 68 | 69 | lazy var windowController = ItemManagementWindowController() 70 | lazy var boxAndItemService = BoxAndItemService() 71 | 72 | func applicationDidFinishLaunching(_ aNotification: Notification) { 73 | windowController.eventHandler = boxAndItemService 74 | 75 | windowController.showWindow(self) 76 | windowController.window?.makeKeyAndOrderFront(self) 77 | } 78 | 79 | `ItemManagementWindowController` simply passes the `BoxAndItemService` down to the `ItemViewController` which actually needs it. 80 | 81 | @IBAction public func addBox(sender: AnyObject) { 82 | guard let eventHandler = self.eventHandler else { 83 | return 84 | } 85 | 86 | let boxId = eventHandler.provisionNewBoxId() 87 | let box = BoxNode(boxId: boxId) 88 | let indexPath = IndexPath(index: nodeCount()) 89 | itemsController.insertObject(box, 90 | atArrangedObjectIndexPath: indexPath) 91 | orderTree() 92 | } 93 | 94 | Now that `addBox(_:)` in `ItemViewController` depends on `eventHandler` being set, the tests have to provide it. The `AddingItemsTests` suite will use the actual `BoxAndItemService`, but the `ItemViewControllerTests` won't. They're unit tests. To make them pass, the unit tests will provide a `NullEventHandler`: a stub to satisfy the dependency. 95 | 96 | class EventHandlerStub: HandlesItemListEvents { 97 | func provisionNewBoxId() -> BoxId { 98 | return BoxId(0) 99 | } 100 | 101 | func provisionNewItemId(inBox boxId: BoxId) -> ItemId { 102 | return ItemId(0) 103 | } 104 | } 105 | 106 | I would've added `EventHandlerStub` to `ItemViewController` directly as the default value of the `eventHandler` property. This way, I wouldn't need to conditionally unwrap the optional. (I don't like the additional level of nesting.) 107 | 108 | Since `eventHandler` is a weak reference only, it isn't possible to add a placeholder object with "null" behavior in `ItemViewController` directly. [Null Objects][nullobj] are pretty useful to avoid `nil` checks. My utter dislike for what conditionally unwrapping optionals does to my code is a strong motivation to employ Null Objects in Swift. This particular interface is heavy on the _query_ side of CQRS while Null Objects are better suited to stub-out _commands_. 109 | 110 | To make the test pass, `CoreDataBoxRepository` needs a `count() -> Int` method: 111 | 112 | public func count() -> Int { 113 | let fetchRequest = NSFetchRequest(entityName: ManagedBox.entityName()) 114 | fetchRequest.includesSubentities = false 115 | 116 | var error: NSError? = nil 117 | let count = managedObjectContext.countForFetchRequest(fetchRequest, 118 | error: &error) 119 | 120 | if count == NSNotFound { 121 | // handle error 122 | return NSNotFound 123 | } 124 | 125 | return count 126 | } 127 | 128 | Here, still, I don't handle errors. Why is that so? Because I don't have an actual application at my disposal which could respond to anything. I could either `fail()` and stop the application on such errors or implement my own error handling facilities. The latter is what I'll discuss and implement [in part 3.](#errorevents). 129 | 130 | Now all tests pass: new `Box`es are created and both stored and shown.[^1d4b7c3ffa9fbd71ee7d64e5aacab39446c90acd] 131 | 132 | 133 | 134 | [^1d4b7c3ffa9fbd71ee7d64e5aacab39446c90acd]: See [commit 1d4b7c3](https://github.com/CleanCocoa/mac-appdev-code/commit/1d4b7c3ffa9fbd71ee7d64e5aacab39446c90acd) 135 | 136 | [nullobj]: http://en.wikipedia.org/wiki/Null_Object_pattern 137 | 138 | -------------------------------------------------------------------------------- /manuscript/part0-intro/500-architecture.txt: -------------------------------------------------------------------------------- 1 | ## Architecture Overview 2 | 3 | Out there, you'll find tons of software architecture approaches and dogmas. I already picked one for this book: a layered clean architecture. It enforces design decisions I found to be very healthy in the long run. With practice, you know where the solution to a problem should be located. And from this context you can derive how to implement something that works. I'll show you how that works throughout the book. For now, I want to introduce the basic framework so we can think in the same terms. 4 | 5 | {width=50%} 6 | ![Layered Hexagonal Architecture](images/201507231024_layered-hex.png) 7 | 8 | For the sake of this book and the rather simple example application, it suffices to think about the following layers, or nested circles: 9 | 10 | * The **Domain** at the core of your application, dealing with business rules and actual algorithms. 11 | * The so-called **Application** layer, where interaction with the Domain's services take place. 12 | * **Infrastructure**, hosting persistence mechanisms, and **User Interface**, living in the outermost layer where the app exposes so-called adapters to the rest of the world. 13 | 14 | ### Domain 15 | 16 | What's the core of an app? 17 | 18 | Is it the user-facing presentational elements? Is it server communication logic? Or is it your money-maker, maybe a unique feature or data crunching algorithm? 19 | 20 | I buy into _Domain Driven Design_ principles and say: the domain is at the core. It's an abstract thing, really. It's the realm of language; that's where we can talk about "items" that are "organized into boxes." Through setting the stage for the example of this book in the last section, I already created a rough sketch of the domain. This is the _problem space._ A model of the domain of boxes and items in code is part of the _solution space._ These are called "domain objects", and they don't know a thing about the Cocoa framework or Core Data. 21 | 22 | #### Entities in the Domain 23 | 24 | `Box` and `Item` are the core parts of this domain. They are **Entities**: objects that have identity over time. Entities are for example opposed to value objects, which are discardable representations. The integer `42` or the string "living-room" are value objects; their equality depends on their value alone. Two Entities with similar values should nevertheless be unequal. If your dog and my dog are both called "Fido", they are still different dogs. Similarly, a user of this app should be able to change the title of two or more existing `Box`es to "living-room" without changing their identity. 25 | 26 | A `Box` is a specific kind of Entity. It's an **Aggregate** of entities (including itself): it has label for its own and takes care of a collection of `Item`s. An `Item` will only exist inside a `Box`, so access to it is depending on knowing its `Box`, first. Being an Aggregate is a strong claim about object dependencies and object access. In a naive approach, you'd query the database for boxes and items independently, combine them for display, and that's it. The notion of Aggregates changes your job: you only request boxes and the items are included automatically. 27 | 28 | To get `Box` Aggregates, a **Repository** with a collection-like interface is used. The `BoxRepository` specifies the interface to get all boxes, remove existing ones, or add new ones. And when it hydrates a `Box` object from the database, it hydrates the corresponding `Item`s, too. Without a Repository, assembling an Aggregate would be very cumbersome. If you know the classic Gang of Four _Design Patterns_, you can think of a Repository in terms of a Factory which takes care of complex object creation. 29 | 30 | #### Services of the Domain 31 | 32 | In the domain of the Word Counter, for example, there reside a lot of other objects. Some deal with incoming **Domain Events** like "a word was typed". There are `Recorder`s which save these events. `Bookkeeper`s hold on to a day's active records. There's a lot of other stuff revolving around the process of counting words. 33 | 34 | In this example application, there won't be much going on at the beginning. We will focus on storing and displaying data. That'll be an example for application wire-framing. There'll be one particular **Domain Service** that encapsulates a user intent: to provision new `Box`es and `Item`s. This equals creating new Entities. This Domain Service adds the Entities to the Repository so other objects can obtain them later on. 35 | 36 | **Services** in general are objects without identity that encapsulate behavior. You may argue they shouldn't even have _state_. They execute commands, but they shouldn't answer queries. That's as general a definition I can give without limiting its application too much. 37 | 38 | I> Although I will use the Repository to generate object identifiers through `nextId()`, there is room for improvement. Acting as a storage and vending identifiers are two concerns, we can argue. You could add an `IdentityService` type which only vends new identifiers. This way, the `BoxRepository` will only deal with object retrieval and storage while another service vends identifiers. 39 | 40 | {pagebreak} 41 | 42 | ### Infrastructure 43 | 44 | The domain itself doesn't implement database access through repositories. It defines the interfaces or protocols, though. The concrete implementation resides elsewhere: in the **Infrastructure** layer. When the Domain Service reaches "outward" through the Repository interface, it sends data through the Domain's "ports". The concrete Core Data Repository implementation is an "adapter" to one of these ports. 45 | 46 | A concrete Repository implementation can be a wrapper for an array, becoming an in-memory storage. It can be a wrapper around a `.plist` file writer, or any other data store mechanism, including Core Data. The Domain doesn't care about the concrete implementation -- _that is a mere detail._ The Domain only cares about the fulfillment of the contract; that's why it has 100% control over the protocol definition. 47 | 48 | All the Core Data code should be limited to this layer. Exposing the dependency on Core Data elsewhere makes the distinction between Domain and Infrastructure useless. To make the persistence mechanism an implementation detail gives you a lot of flexibility and keeps the rest of the app cleaned from knowledge of this detail. But all the planning and architecture won't help if you blur the boundaries again and sprinkle Core Data here and there because it'd be more convenient. 49 | 50 | ### Application 51 | 52 | Your Domain contains all the business logic. It provides interfaces to clients through Aggregates and Services. A layer around this core functionality is called the **Application layer**, because it executes functionality of the Domain. 53 | 54 | #### The Client of the Domain 55 | 56 | The Domain contains a Service object to provision new Entities. But someone has to call the Domain Service. The application's user is pressing a button, but how does the button-press translate to using the Domain Services? While we're at it: where does the view controller come from, where is it created and which objects holds a strong reference to it? 57 | 58 | This is where the Application layer comes into play. It contains **Application Service** objects which hold on to view controllers and Domain Services, for example. The Application layer is the client of the Domain's functionality. 59 | 60 | When the "Add Box" button is pressed, the view controller translates this interaction into a command for the Application layer, which in turn prepares everything to make the Domain provision a new `Box` instance. `Box`es have to be reported to the user interface for display, too. That's what the Application layer will deal with in use case-centered Service objects. 61 | 62 | #### Glue-Code 63 | 64 | You may have asked yourself how the Domain will know which Repository implementation to use in the actual program. The Application layer can take care of this when it initializes and uses the Domain's services. 65 | 66 | "[Dependency Injection][depinj]" is the fancy term for passing a concrete Repository implementation to an object that requires a certain protocol. It's a trivial concept when you see it in action: 67 | 68 | {linenos=off} 69 | func jsonify(serializer: JSONSerializer) -> JSON { 70 | serializer.add(self.title) 71 | serializer.add(self.items.map { $0.title }) 72 | return serializer.json() 73 | } 74 | 75 | Instead of creating the `JSONSerializer` inside the method, the serializer is _injected_. Whenever the function that uses an objects also creates it, you'll have a hard time testing its behavior -- and that results in a harder time switching collaborating objects later, too. 76 | 77 | Apart from this simple application of passing a collaborator around, Dependency Injection as a paradigm can be used to create object clusters, too. Instead of passing in the dependency with a method call, you pass it as a parameter of the initializer. You probably do things like that in your `AppDelegete` already to wire multiple objects together and pass them to view controllers or navigation controllers. You can use a Dependency Injection library to automate this setup task and configure the dependency network outside your code, but I always found these things to be way too cumbersome. (Then again, I never built huge enterprise software which may benefit from this kind of flexibility.) This book's example app isn't complex enough to warrant more than a global storage object for the main dependencies that are going to be used in the Application Services. The Repository implementations are set up on a **Service Locator** which is usually is a global singleton. I put the Service Locator inside the Infrastructure layer because it mostly consists of Infrastructure parts anyway. 78 | 79 | Thus, Infrastructure deals with providing persistence mechanisms to be plugged them into the Domain. Actually calling the methods to set up Domain Services is a matter of the use case objects which I put into the Application layer. 80 | 81 | [depinj]: http://en.wikipedia.org/wiki/Dependency_injection 82 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/600-functional tests.txt: -------------------------------------------------------------------------------- 1 | ## Functional Testing Helps Transition to Adding Real Features 2 | 3 | I'm pretty confident the view does what it should do. It's all just basic stuff, although it took enough trial & error to reach this point. (I am talking about you, [explicit Cocoa Bindings](#x20141124163838) which break the view's behavior!) 4 | 5 | I am curious if the app will work, though. This is only natural. Developers want to have a running prototype as early as possible. I'm no exception. Unit tests can help to get feedback that algorithms work. But you won't know if the app executes and performs anything at all until you run it. There are two steps I could take at this point to quench my curiosity. 6 | 7 | First, I could use more unit tests to verify that view model updates result in `NSOutlineView` updates. These updates should also be visible when I run the app, but the unit tests won't give visual feedback. Still, I would verify that the controller logic works. Since the data doesn't change in the background but depend on me clicking on widgets, I'm eager to see Cocoa Bindings in action. Then again, I've already witnessed Cocoa Bindings in another project. It felt like magic when the label updated to a new value that the component received after a couple of idle time automatically. Key-Value Observing is doing a lot of the hard work here. I can assure you that the Cocoa Bindings are set up correctly; in fact, the binding tests verify that the setup is working. Seeing the changes in the running app equals a manual integration test -- that is testing Apple's internal frameworks to do their job properly. That doesn't make any sense. I would like to see it in action, but it won't provide any useful information. 8 | 9 | Second, I could add functional tests to the test harness. And this is what I'll do: in a fashion you usually find with Behavior-Driven Development (as opposed to Test-Driven Development, where you start at the innermost unit level), I'll add a failing test which will need the whole app to work together in order to pass. It's a test which is going to be failing for quite a while. It's an automated integration test that exercises various layers of the app at once. 10 | 11 | In the past I wrote failing functional tests for web applications only because they would drive out the REST API or user interaction pretty well. I haven't tried this for iOS or Mac applications, yet. So let's do this. 12 | 13 | If you work by yourself, I think it's okay to check in your code with a failing test as long as it's guiding development. Don't check in failing unit tests just because you can't figure out how to fix the problem immediately. Everything goes as long as you don't push the changes to a remote repository. Until that point, you can always alter the commit history. 14 | 15 | Having a failing functional test at all may not be okay with your team mates, though. After all, your versioning system should always be in a valid state, ready to build and to pass continuous deployment. Better not check in the failing test itself, then, or work on a local branch exclusively until you rebase your commit history when your feature is ready. 16 | 17 | The bad thing about functional tests or integration tests is this: if all you had were functional tests, you'd have to write a _ton_ of them. With every condition, with every fork in the path of execution, the amount of functional tests to write grows by a factor of 2. Functional tests grow exponentially. That's bad. 18 | 19 | So don't rely too much on them. Unit tests are the way to go. 20 | 21 | And make sure you watch [J. B. Rainsberger's "Integrated Tests are a Scam"](https://vimeo.com/80533536) some day soon. It's really worth it. 22 | 23 | That being said, let's create a functional test to learn how things work together and guide development. 24 | 25 | ### First Attempt at Capturing the Expected Outcome 26 | 27 | I want to go from user interface actions all down to Core Data. That's a first step. This is the test I came up with: 28 | 29 | func testAddFirstBox_CreatesBoxRecord() { 30 | // Precondition 31 | XCTAssertEqual(repository.count(), 0, "repo starts empty") 32 | 33 | // When 34 | viewController.addBox(self) 35 | 36 | // Then 37 | XCTAssertEqual(repository.count(), 1, "stores box record") 38 | XCTAssertEqual(allBoxes().first?.title, "New Box") 39 | } 40 | 41 | There's room for improvement: I expect that there's a record with a given `BoxId` afterwards, and that the view model contains a `BoxNode` with the same identifier. This is how the view, domain, and Core Data stay in sync. But the naive attempt at querying the `NSTreeController` is hideous: 42 | 43 | {linenos=off} 44 | viewController.itemsController.arrangedObjects.childNodes!!.first.boxId 45 | 46 | I rather defer such tests until later to avoid these train wreck-calls and stick with the basic test from above that at least indicates the `count()` did change, even though I don't know if the correct entity was inserted at this point. 47 | 48 | ### Making Optionals-based Tests Useful 49 | 50 | On a side note, I think Swift's optionals are making some tests weird. With modern Swift, optional chaining and `XCTAssertEqual` play together nicely: 51 | 52 | {linenos=off} 53 | XCTAssertEqual(allBoxes().first?.title, "New Box") 54 | 55 | Sometimes, you need to unwrap an optional, though. Force-unwrapping `nil` results in a runtime error which we want to avoid during tests because it interrupts execution of the whole test suite. A failure is preferable. 56 | 57 | {linenos=off} 58 | if let box = allBoxes().first { 59 | XCTAssertEqual(box.title, "New Box") 60 | } else { 61 | XTCFail("expected 1 or more boxes") 62 | } 63 | 64 | Imagine `allBoxes()` returned an optional; then the complexity of the test would increase a lot: 65 | 66 | {linenos=off} 67 | if let boxes = allBoxes() { 68 | if let box: ManagedBox = allBoxes().first { 69 | XCTAssertEqual(box.title, "New Box") 70 | } else { 71 | XCTFail("no boxes found") 72 | } 73 | } else { 74 | XCTFail("boxes request invalid") 75 | } 76 | 77 | When you work with collections, instead of `nil`, return an empty array. This makes the result much more predictable for the client. Here's the updated version: 78 | 79 | {title="Don't allow optionals if there's no need to"} 80 | func allBoxes() -> [ManagedBox] { 81 | let request = NSFetchRequest(entityName: ManagedBox.entityName()) 82 | let results: [AnyObject] 83 | 84 | do { 85 | try results = context.fetch(request) 86 | } catch { 87 | XCTFail("fetching all boxes failed") 88 | return [] 89 | } 90 | 91 | guard let boxes = results as? [ManagedBox] else { 92 | return [] 93 | } 94 | 95 | return boxes 96 | } 97 | 98 | If optional chaining doesn't work for you for some reason, make it a two-step assertion instead: 99 | 100 | {linenos=off} 101 | let box: ManagedBox = allBoxes().first 102 | XCTAssertNotNil(box) 103 | if let box = box { 104 | XCTAssertEqual(box.title, "New Box") 105 | } 106 | 107 | If the value is `nil`, the first assertion will fail and the test case will be marked as a failing test. You can come back to it and fix it. No breaking runtime errors required! 108 | 109 | ### Wire-Framing the Path to "Green" 110 | 111 | Now this integration test fails, of course. In broad strokes, this is what's left to do to connect the dots and make it pass: 112 | 113 | * I have to make a repository available to the view. I'll do this via **Application Service**s. 114 | * I have to add an actual Application Service layer. Remember, this is the client of the domain. 115 | * The Application Service will issue saving `Box`es and `Item`s without knowing about Core Data. 116 | * I need a service provider of sorts. Something somewhere has to tell the rest of the application that `CoreDataBoxRepository` (from Infrastructure) is the default implementation of the `BoxRepository` protocol (from the Domain). The process of setting this up takes place in the application delegate, but there's a global `ServiceLocator` singleton missing to do the actual look-up. 117 | * I may need to replace the `ServiceLocator`'s objects with test doubles. 118 | 119 | The `ServiceLocator` can look like this: 120 | 121 | {title="ServiceLocator singleton to select default implementations"} 122 | open class ServiceLocator { 123 | open static let sharedInstance = ServiceLocator() 124 | 125 | // MARK: Configuration 126 | 127 | fileprivate var managedObjectContext: NSManagedObjectContext? 128 | 129 | public func setManagedObjectContext(_ managedObjectContext: NSManagedObjectContext) { 130 | precondition(self.managedObjectContext == nil, 131 | "managedObjectContext can be set up only once") 132 | 133 | self.managedObjectContext = managedObjectContext 134 | } 135 | 136 | // MARK: Dependencies 137 | 138 | public class func boxRepository() -> BoxRepository { 139 | return sharedInstance.boxRepository() 140 | } 141 | 142 | // Override this during tests: 143 | open func boxRepository() -> BoxRepository { 144 | guard let managedObjectContext = self.managedObjectContext 145 | else { preconditionFailure("managedObjectContext must be set up") } 146 | 147 | return CoreDataBoxRepository(managedObjectContext: managedObjectContext) 148 | } 149 | } 150 | 151 | It's not very sophisticated, but it is enough to decouple the layers. An Application Service could now perform insertions like this: 152 | 153 | public func provisionBox() -> BoxId { 154 | let repository = ServiceLocator.boxRepository() 155 | let boxId = repository.nextId() 156 | let box = Box(boxId: boxId, title: "A Default Title") 157 | 158 | repository.addBox(box) 159 | 160 | return boxId 161 | } 162 | 163 | This is not a command-only method because it returns a value. Instead of returning the new ID so the view can add it to the node, the service will be responsible for actually adding the node to the view. That'd be the first major refactoring.[^cf861676e8ce37ec13bce12999f7e778f2ce081d] 164 | 165 | 166 | It's about time to leave the early set-up phase and enter Part II, where I'm going to deal with all these details and add the missing functionality. 167 | 168 | [^cf861676e8ce37ec13bce12999f7e778f2ce081d]: See [commit cf86167](https://github.com/CleanCocoa/mac-appdev-code/commit/cf861676e8ce37ec13bce12999f7e778f2ce081d) 169 | -------------------------------------------------------------------------------- /manuscript/part3-domain/100-events.txt: -------------------------------------------------------------------------------- 1 | ## Introducing Events in Place of Notifications {#domainevents} 2 | 3 | In the last part, I ended up using `NotificationCenter` to send events. Notifications are easy to use and Foundation provides objects that are well-known. I don't like how working with a notification's `userInfo` dictionary gets in the way in Swift, though. Objective-C was very lenient when you used dictionaries. That introduced a new source of bugs, but if you enjoy dynamic typing, it worked very well. Swift seems to favor new paradigms that enforce strong typing. 4 | 5 | ### Swift Is Sticking Your Head Right at the Problem 6 | 7 | In Objective-C, I'd send and access the "Box was created" event info like this: 8 | 9 | {linenos=off} 10 | // Sending 11 | NSDictionary *userInfo = @[@"boxId": @(boxId.identifier)]; 12 | [notificationCenter postNotificationName:kBoxProvisioned, 13 | object:self, 14 | userInfo:userInfo]; 15 | 16 | // Receiving 17 | int64_t identifier = notification.userInfo["boxId"].longLongValue; 18 | [BoxId boxIdWithIdentifier:identifier]; 19 | 20 | Swift 3 allows sending notifications with with number value types directly. We don't have to wrap them in `NSNumber` anymore. Still, the force-unwrapping and the forced cast make me nervous in Swift because they point out a brittle piece of code: 21 | 22 | {linenos=off} 23 | // Sending 24 | let userInfo = ["boxId" : boxId.identifier] 25 | notificationCenter.post(name: kBoxProvisioned, 26 | object: self, userInfo: userInfo) 27 | 28 | // Receiving 29 | let boxInfo = notification.userInfo!["boxId"] as! NSNumber 30 | let identifier = boxInfo.int64Value 31 | let boxId = BoxId(identifier: identifier) 32 | 33 | I settled for sending IDs only because putting ID and title makes things complicated for the "Item was created" event. There, I'd have to use nested dictionaries. A JSON representation would look like this: 34 | 35 | {title="JSON representation of the event data", linenos=off, lang=json} 36 | { 37 | box: { 38 | id: ... 39 | } 40 | item: { 41 | id: ... 42 | title: "the title" 43 | } 44 | } 45 | 46 | Accessing nested dictionaries in Swift is even worse, though, so I settled with supplying two IDs only. On the downside, every client now has to fetch data from the repository to do anything with the event. That's nuts. 47 | 48 | The relative pain I experience with Swift here highlights the problems Objective-C simply assumed we'd take care of: there could be no `userInfo` at all, there could be no value for a given key, and there could be a different kind of value than you expect. 49 | 50 | It's always a bad idea to simply assume that the event publisher provided valid data in dictionaries. Force-unwrapping and force-casting will accidentally break sooner or later. What if you change dictionary keys in the sending code but forgot to update all client sites? Defining constants remedies the problem a bit. But the structure of a dictionary is always opaque, and if you change it, you have to change multiple places in your code. It's a good code heuristic to look for changes that propagate through your code base. If you have to touch more than 1 place to perform a change, that's an indicator of worse than optimal encapsulation: these co-variant parts in your app depend on one another but don't show their dependency explicitly. 51 | 52 | So you have to perform sanity checks to catch invalid events anyway, in Objective-C just as much as in Swift. 53 | 54 | Using real event objects will work wonders. Serializing them into dictionaries and de-serializing `userInfo` into events will encapsulate the sanity checks and provide usable interfaces tailored to each event's use. There's only one place you need to worry about if you want to change the nature of an event. 55 | 56 | {pagebreak} 57 | 58 | 59 | ### Event Value Types 60 | 61 | An event should be a value type, and thus a struct. It assembles a `userInfo` dictionary. For `NotificationCenter` convenience, it also assembles a `Notification` object: 62 | 63 | {title="Domain Event serializing itself into userInfo dictionary"} 64 | // Provide a typealias for brevity and readability 65 | public typealias UserInfo = [AnyHashable : Any] 66 | 67 | public struct BoxProvisionedEvent: DomainEvent { 68 | 69 | public static let eventName = Notification.Name( 70 | rawValue: "Box Provisioned Event") 71 | 72 | public let boxId: BoxId 73 | public let title: String 74 | 75 | public init(boxId: BoxId, title: String) { 76 | self.boxId = boxId 77 | self.title = title 78 | } 79 | 80 | public init(userInfo: UserInfo) { 81 | let boxIdentfier = userInfo["id"] as! IntegerId 82 | let title = userInfo["title"] as! String 83 | self.init(boxId: BoxId(boxIdentfier), title: title) 84 | } 85 | 86 | public func userInfo() -> UserInfo { 87 | return [ 88 | "id" : boxId.identifier, 89 | "title" : title 90 | ] 91 | } 92 | } 93 | 94 | The underlying protocol is really simple: 95 | 96 | public protocol DomainEvent { 97 | static var eventName: Notification.Name { get } 98 | 99 | init(userInfo: UserInfo) 100 | func userInfo() -> UserInfo 101 | } 102 | 103 | When publishing an event, these properties can be used to convert to a `Notification`. I used to use a free `notification(_:)` function for that: 104 | 105 | {linenos=off} 106 | func notification(event: T) -> Notification { 107 | return Notification(name: T.eventName, object: nil, 108 | userInfo: event.userInfo()) 109 | } 110 | 111 | Now with protocol extensions, the conversion can be coupled more closely to the `DomainEvent` protocol, getting rid of the free function: 112 | 113 | {linenos=off} 114 | extension DomainEvent { 115 | public func notification() -> Notification { 116 | return Notification( 117 | name: type(of: self).eventName, 118 | object: nil, 119 | userInfo: self.userInfo()) 120 | } 121 | 122 | func post(notificationCenter: NotificationCenter) { 123 | notificationCenter.post(self.notification()) 124 | } 125 | } 126 | 127 | See the convenience method `post(notificationCenter:)`? That can come in handy when testing the actual sending of an event if you override it in the tests. 128 | 129 | Now `BoxProvisionedEvent` wraps the `Notification` in something more meaningful to the rest of the app. It also provides convenient accessors to its data, the ID and title of the newly created box. That's good for slimming-down the subscriber: no need to query the repository for additional data. 130 | 131 | There's a `DomainEventPublisher` which takes care of the actual event dispatch. We'll have a look at that in a moment. With all these changes in place, the `DisplayBoxesAndItems` Application Service now does no more than this: 132 | 133 | class DisplayBoxesAndItems { 134 | var publisher: DomainEventPublisher! { 135 | return DomainEventPublisher.sharedInstance 136 | } 137 | 138 | // ... 139 | 140 | func subscribe() { 141 | let mainQueue = OperationQueue.mainQueue() 142 | 143 | boxProvisioningObserver = publisher.subscribe( 144 | BoxProvisionedEvent.self, queue: mainQueue) { 145 | [weak self] (event: BoxProvisionedEvent!) in 146 | 147 | let boxData = BoxData(boxId: event.boxId, title: event.title) 148 | self?.consumeBox(boxData) 149 | } 150 | 151 | // ... 152 | } 153 | 154 | func consumeBox(boxData: BoxData) { 155 | consumer?.consume(boxData) 156 | } 157 | 158 | // ... 159 | } 160 | 161 | The `subscribe` method is interesting. Thanks to [Swift generics][generics], I can specify an event type using `TheClassName.self` (the equivalent to `[TheClassName class]` in Objective-C) and pipe it through to the specified block to easily access the values. 162 | 163 | The conversion of `Notification` to the appropriate domain event takes place in the `DomainEventPublisher`: 164 | 165 | func subscribe( 166 | _ eventKind: T.Type, 167 | queue: OperationQueue, 168 | usingBlock block: (T) -> Void) 169 | -> DomainEventSubscription { 170 | 171 | let eventName: String = T.eventName 172 | let observer = notificationCenter.addObserver(forName: eventName, 173 | object: nil, queue: queue) { 174 | notification in 175 | 176 | let userInfo = notification.userInfo! 177 | let event: T = T(userInfo: userInfo) 178 | block(event) 179 | } 180 | 181 | return DomainEventSubscription(observer: observer, eventPublisher: self) 182 | } 183 | 184 | It takes some getting used to Swift to read this well. I'll walk you through it. 185 | 186 | Let's stick to the client code from above and see what subscribing to `BoxProvisionedEvent`s does: 187 | 188 | * The type of the `eventKind` argument should the type (not instance!) of a descendant of `DomainEvent`. That's what `BoxProvisionedEvent.self` is. You don't pass in an actual event, but it's class (or "type"). Interestingly, this value is of no use but to set `T`, the generics type placeholder. 189 | * The `block` (line 3) yields an event object of type `T` (which becomes an instance of `BoxProvisionedEvent`, for example) 190 | * The `eventName` (line 4) will be, in this example case, `Box Provisioned Event`. `DomainEvent`s have a property called `eventName` to return a string which becomes the notification name. 191 | * The actual observer is a wrapper around the `block` specified by the client. The wrapper creates an event of type `T`. All `DomainEvent`s must provide the deserializing initializer `init(userInfo: [Hashable : Any])`, and so does `T`. 192 | 193 | When a `BoxProvisioned` event is published, it is transformed into a `Notification`. The notification is posted as usual. The wrapper around the client's subscribing block receives the notification, de-serializes a `BoxProvisioned` event again, and provides this to the client. 194 | 195 | `DomainEventSubscription` is a wrapper around the observer instances `NotificationCenter` produces. This wrapper unsubscribes upon deinit automatically, so all you have to do is store it in an attribute which gets nilled-out at some point. 196 | 197 | It took some trial and error to get there, but it works pretty well.[^d0a8c7b56296961a662bd1681b134dfe550adfff] 198 | 199 | 205 | 206 | [^d0a8c7b56296961a662bd1681b134dfe550adfff]: See the [latest commit](https://github.com/CleanCocoa/mac-appdev-code/commit/3c8ff3835df3c8262833ff9944dedf81aa7d4e94), or the initial [commits d0a8c7b](https://github.com/CleanCocoa/mac-appdev-code/commit/d0a8c7b56296961a662bd1681b134dfe550adfff), [9a8f41d](https://github.com/CleanCocoa/mac-appdev-code/commit/9a8f41d29b1bd48eb1d6f7475699da0d0d925c2b), [a2b0f4c](https://github.com/CleanCocoa/mac-appdev-code/commit/a2b0f4c578abbe4407d5cc06b7fc423472cb8151), and [74cf804](https://github.com/CleanCocoa/mac-appdev-code/commit/74cf8041d561e6ecbe696dbb8f9c5d6046005405) 207 | 208 | [generics]: http://swiftyeti.com/generics/ 209 | 210 | -------------------------------------------------------------------------------- /manuscript/part1-bootstrapping/200-fleshing out interfaces.txt: -------------------------------------------------------------------------------- 1 | ## Introduce Repository and Its Collaborators for Tests 2 | 3 | `Box` and `Item` objects will each have their own IDs. To make my life easier, IDs will be unique random 64-bit integers. Each new ID has to be checked against the pool of existing objects to achieve uniqueness. That's the responsibility of a Repository. 4 | 5 | To test that the Repository does return unique IDs, we have to test that it first checks a random ID against the pool of existing ones, and then generates new ones until the generated ID is free. 6 | 7 | I> As I said earlier, in the section about _Domain-Driven Design_ vocabulary, the ID generation could fit nicely into an `IdentityService`. That will make the Repository do one thing less, so the code will adhere more to the _Single Responsibility Principle_. That's not an end in itself, though: splitting the concerns may introduce further problems, like dealing with 2 instead of 1 type. Leaving it in the Repository makes things easier until the application grows more complex. The concrete Repository knows which field and which table of the database to use for an ID already. It can just as well care for generating a new ID, which is mostly about discarding IDs that are in use until a free ID turns up. 8 | 9 | Let's say `nextId()` shall return a unique ID. `integerId()` then is a private method to generate an ID which `nextId()` has to check for uniqueness. 10 | 11 | To test this with a replacement ID generator, you can do the following: 12 | 13 | 1. Take two pre-defined IDs in your test. 14 | 2. Make the ID generator return the first ID on the first call, the second ID on each consecutive call. 15 | 3. Use the faux ID generator in the Repository. Also make it so that the first ID is known to be existing. 16 | 3. Call `nextId()` on the Repository only once and assume that the second ID is returned, thus verifying that the Repository does check the ID generator's results and tries again. 17 | 18 | I would've wanted to override the ID generating method `integerId()` in a test Repository subclass. When I create subclasses of my own classes in tests, though, the subclasses can only override methods which are public, since the tests reside in another module than the application itself. This way, overriding private methods is not an option to change the behavior of a class. Weirdly enough, it worked well in Objective-C. 19 | 20 | Now that I have to think of my code as a module with a public interface, the old testing strategies involving method overrides have to be solved differently.[^extrant] 21 | 22 | [^extrant]: Michael C. Feathers calls the following dependency breaking technique "Extract Interface" in his book _Working Effectively with Legacy Code_. See the [book list](#booklist) for details. 23 | 24 | I want to ensure the repository generates IDs until an ID is free. Duplicates are not allowed. With a randomized ID generator, this is very hard to test. I could still override the `integerId()` method when I make it public, or leave it with internal access level to override it when I import the source as `@testable`: 25 | 26 | // Source module 27 | public protocol BoxRepository { 28 | func nextId() -> BoxId 29 | // ... non-id related functions here ... 30 | } 31 | 32 | open class CoreDataBoxRepository: BoxRepository { 33 | // ... 34 | 35 | public func nextId() -> BoxId { 36 | return BoxId(integerId()) 37 | } 38 | 39 | // Override during tests to add testable replacement algorithm 40 | func integerId() -> IntegerId { 41 | // return random 64-bit integer 42 | } 43 | } 44 | 45 | // Test module 46 | @testable import TheApp 47 | 48 | class TestRepository : CoreDataBoxRepository { 49 | var firstId: IntegerId! 50 | var secondId: IntegerId! 51 | 52 | private var integerIdCalls = 0 53 | override func integerId() -> IntegerId { 54 | defer { integerIdCalls += 1 } 55 | 56 | if integerIdCalls == 0 { 57 | return firstId 58 | } 59 | 60 | return secondId 61 | } 62 | } 63 | 64 | With this `TestRepository`, I _know_ the value of `firstId`. I can reserve it and test that the repository ensures another attempt is made at finding a free ID, resulting in `secondId` being returned by a sole call of `nextId()`. 65 | 66 | Making ID-generation public is a bad idea. It was the only way before Swift 2, though. It's an implementation detail of the Repository I don't want to expose. When dealing with legacy code, sometimes you have no choice but alter the public interface of a class and promise to yourself to not violate certain rules. We're not dealing with legacy code, though. We aim for a clean API. So what else can we do to make the ID generation algorithm exchangeable? 67 | 68 | ### Extract ID Generation 69 | 70 | First, recognize ID generation as a distinct responsibility and extract it into its own interface: 71 | 72 | {linenos=off} 73 | public protocol GeneratesIntegerId { 74 | func integerId() -> IntegerId 75 | } 76 | 77 | Provide a default but private implementation. I used `struct` instead of `class` because there's no _state_ associated with this object. It's simply an object with a single method, no state, no sense of past and future, and no dependencies: 78 | 79 | {linenos=off} 80 | struct DefaultIntegerIdGenerator: GeneratesIntegerId { 81 | func integerId() -> IntegerId { 82 | // return random 64-bit integer 83 | } 84 | } 85 | 86 | The `CoreDataBoxRepository`'s interface doesn't have to change unless needed. Simply create another designated initializer and mark the old one as a "convenience" initializer: 87 | 88 | {title="Repository with ID generator that can be replaced with a double in tests"} 89 | public class CoreDataBoxRepository: BoxRepository { 90 | let managedObjectContext: NSManagedObjectContext 91 | let integerIdGenerator: GeneratesIntegerId 92 | 93 | public convenience init(managedObjectContext: NSManagedObjectContext) { 94 | self.init(managedObjectContext: managedObjectContext, 95 | integerIdGenerator: DefaultIntegerIdGenerator()) 96 | } 97 | 98 | public init(managedObjectContext: NSManagedObjectContext, 99 | integerIdGenerator: GeneratesIntegerId) { 100 | self.managedObjectContext = managedObjectContext 101 | self.integerIdGenerator = integerIdGenerator 102 | } 103 | 104 | // ... 105 | 106 | public func nextId() -> ItemId { 107 | return ItemId(unusedIntegerId()) 108 | } 109 | 110 | fileprivate func unusedIntegerId() -> IntegerId { 111 | var identifier: IntegerId 112 | 113 | do { 114 | identifier = integerId() 115 | } while integerIdIsTaken(identifier) 116 | 117 | return identifier 118 | } 119 | 120 | fileprivate func integerId() -> IntegerId { 121 | return integerIdGenerator.integerId() 122 | } 123 | 124 | fileprivate func integerIdIsTaken(identifier: IntegerId) -> Bool { 125 | // check if an entity with the identifier is already present 126 | } 127 | } 128 | 129 | 130 | {pagebreak} 131 | 132 | Now I can provide a test generator which helps to verify the Repository's behavior: 133 | 134 | class TestIntegerIdGenerator: GeneratesIntegerId { 135 | let firstAttempt: IntegerId = 1234 136 | let secondAttempt: IntegerId = 5678 137 | var callCount = 0 138 | 139 | func integerId() -> IntegerId { 140 | let identifier = (callCount == 0 ? firstAttempt : secondAttempt) 141 | 142 | callCount += 1 143 | 144 | return identifier 145 | } 146 | } 147 | 148 | And here's the test which puts the test generator into place via Dependency Injection (which means we pass the generator to the Repository initializer): 149 | 150 | func testNextId_WhenGeneratedIdIsTaken_ReturnsAnotherId() { 151 | // The test suite has a discardable `context` property to 152 | // verify the Core Data queries work properly: 153 | let existingId = ItemId(testGenerator.firstAttempt) 154 | ManagedItem.insertManagedItem(existingId, 155 | title: "irrelevant", 156 | inManagedObjectContext: self.context) 157 | 158 | // Dependency setup 159 | let testGenerator = TestIntegerIdGenerator() 160 | let repository = CoreDataBoxRepository( 161 | managedObjectContext: self.context, 162 | integerIdGenerator: testGenerator) 163 | 164 | let itemId = repository.nextId() 165 | 166 | let expectedNextId = ItemId(testGenerator.secondAttempt) 167 | XCTAssertEqual(itemId, expectedNextId, 168 | "Should generate another ID because first one is taken") 169 | } 170 | 171 | Strangely enough, I have the impression that Swift forces me to make good decisions design-wise, while I expect more experienced Cocoa developers to consider all of this backward, or ugly. I expect them to think this is all too much like Java: create a lot of interfaces (format protocols) and service objects and stuff like that. 172 | 173 | The code benefits from small, stateless objects like these. They make the model richer and keep the interface lean. 174 | 175 | ### Extract the ID Generation Retry Sequence 176 | 177 | The `CoreDataBoxRepository` has to check its `NSManagedObjectContext` for an existing ID. There's no way around that. But it doesn't have to implement the retry loop itself. We can extract this into a helper object. All that `CoreDataBoxRepository` will provide is: 178 | 179 | 1. knowledge about how to generate an ID (which may or may not be takes), and 180 | 2. knowledge about how to check if an ID is taken. 181 | 182 | If we abstract these two things away, we end up with the `GeneratesIntegerId` protocol again, plus a new kind of "ID is taken"-checker. Instead of making an object or protocol of this, we can model this as a closure of type `(IntegerId) -> Bool`. This type makes more sense when viewed as a container property with a name, like `let integerIdIsTaken: (IntegerId) -> Bool`, or as a function that fits the requirement, like `func hasManagedBox(withIdentifier identifier: IntegerId) -> Bool`. 183 | 184 | The extracted type is a mere refactoring of the algorithm we had above. It's generic over `Identifiable`, the base protocol of an actual `BoxId` or `ItemId`, to make the algorithm truly reusable in different contexts: 185 | 186 | struct IdGenerator { 187 | let integerIdGenerator: GeneratesIntegerId 188 | let integerIdIsTaken: (IntegerId) -> Bool 189 | 190 | func nextId() -> Id { 191 | return Id(unusedIntegerId()) 192 | } 193 | 194 | func unusedIntegerId() -> IntegerId { 195 | var identifier: IntegerId 196 | 197 | repeat { 198 | identifier = integerId() 199 | } while integerIdIsTaken(identifier) 200 | 201 | return identifier 202 | } 203 | 204 | func integerId() -> IntegerId { 205 | return integerIdGenerator.integerId() 206 | } 207 | } 208 | 209 | The `CoreDataBoxRepository` provides the actual dependencies: 210 | 211 | {linenos=off} 212 | open func nextId() -> BoxId { 213 | /// Helper function to determines if the ID is taken 214 | func hasManagedBoxWithUniqueId(identifier: IntegerId) -> Bool { 215 | return self.managedBoxWithUniqueId(identifier) != nil 216 | } 217 | 218 | let generator = IdGenerator( 219 | // This is the generator we passed into `CoreDataBoxRepository`: 220 | integerIdGenerator: integerIdGenerator, 221 | 222 | // This is the helper from above: 223 | integerIdIsTaken: hasManagedBoxWithUniqueId) 224 | return generator.nextId() 225 | } 226 | 227 | And thus we have extracted dependencies that accumulated in private methods into new types: the generator became the `GeneratesIntegerId` protocol with the `DefaultIntegerIdGenerator` implementation, and guaranteeing uniqueness moved into `IdGenerator`. 228 | -------------------------------------------------------------------------------- /manuscript/part4-coredata/300-core data in ui.txt: -------------------------------------------------------------------------------- 1 | ## Core Data in the UI 2 | 3 | Since we're coding for Mac, we have the advantage of Cocoa Bindings a lot of iOS developers miss. 4 | 5 | I have mixed feelings about this. Data- or database-centric applications can be wired with Cocoa Bindings. That's simple and effective. But it's hard to catch bugs during runtime, and it's not very intuitive to write unit tests for Cocoa Bindings as we have already seen. Integration tests will do a better job exercising the UI/Cocoa Bindings seam. I try to avoid Cocoa Bindings when the reduce in boilerplate code is minimal. And I try to limit them to the UI layer of the app so problems don't bubble up through all of my application. -- Which requires a clean separation of layers, of course. 6 | 7 | But here, we'll use Cocoa Bindings for as much work as possible to see what the canonical Cocoa way of doing things can look like. 8 | 9 | I> To separate progress on this front from removing the simple Domain Model in the last chapter, I will push changes to another branch of the repository, called [`core-data-ui`](https://github.com/CleanCocoa/mac-appdev-code/tree/core-data-ui) so you can follow along. I keep it around to provide a history. If you're interested in the end result that you can run, though, the "CoreDataOnly" target of the example project is the place to look for. It's always up to date. 10 | 11 | Progress will now be synonymous to degradation. We will mix things up a lot and remove boundaries. 12 | 13 | ### Getting Rid of Overhead 14 | 15 | Since Cocoa Bindings are read--write-bindings, we no longer need to pass Data Transfer Objects (DTO) to the view to display data. `BoxData` and `ItemData` will just be gone. 16 | 17 | This alone produces a lot of change and ripples. 18 | 19 | #### Simplifying ManageBoxesAndItems 20 | 21 | `ManageBoxesAndItems` populates the view with data. Since there's no DTO anymore, most of its behavior is gone. It simply shows the window. The window then takes care of its contents. 22 | 23 | To keep the illusion of boundaries present, I want to let `ManageBoxesAndItems` pass its `BoxRepository` to the view. The `ItemViewController` could just as well fetch this from the `ServiceLocator` itself. But that would intertwine the UI component with Infrastructure. I don't want to make the code worse than it needs to be. This also helps illustrate how you can keep knowledge like this out of your view controllers. 24 | 25 | #### Removing HandleBoxAndItemModifications 26 | 27 | `HandleBoxAndItemModifications` is responsible for creating entities and changing their names. 28 | 29 | When Cocoa Bindings are set up properly, all of this is done behind the scenes by the `NSTreeController`. We can safely get rid of this class completely. 30 | 31 | Gone is the notion of an `eventHandler` for the view. Gone is that attribute from `ManageBoxesAndItems`. We don't even need event handling protocols. This Application Service now only wraps showing the window. We can just as well rename it to something like `ShowItemManagementWindow`. 32 | 33 | #### Removing ProvisioningService 34 | 35 | Since there's no event handler anymore, there's no object left which uses `ProvisioningService`. We can get rid of it, too. 36 | 37 | `ProvisioningService` was where actual repository interactions took place. These will now have to move into the view controller directly. We could keep that service alive, but so far there's not much the view controller is going to delegate. Most changes to the actual database store are "magical": the framework takes care of that for us. 38 | 39 | We only need the repository for a few things: 40 | 41 | * to generate new IDs, 42 | * to add `Box` instances, and 43 | * to remove `Box`es. 44 | 45 | Item management is still the responsibility of the `Box` Aggregate. And since that Aggregate is now aware of Core Data directly, creating an item and persisting the change is as simple as calling: `box.addItemWithId(itemId, title: "the title")`. 46 | 47 | This means we can get rid of existing but unused methods, namely: 48 | 49 | * `func boxes() -> [BoxType]` 50 | * `func boxWithId(_ boxId: BoxId) -> BoxType?` 51 | * `func count() -> Int` 52 | 53 | All of this is taken care of by the `NSTreeController` binding to Core Data. The `BoxRepository` protocol is now just this: 54 | 55 | public protocol BoxRepository { 56 | func nextId() -> BoxId 57 | func nextItemId() -> ItemId 58 | func addBoxWithId(_ boxId: BoxId, title: String) 59 | func removeBox(boxId: BoxId) 60 | } 61 | 62 | #### Removing BoxNode and ItemNode 63 | 64 | We're going to bind the underlying data via `NSTreeController` directly to a `NSManagedObjectContext`. There's no need to provide custom node objects. 65 | 66 | The node objects were an abstraction of the data as a _view model_. Since the true entities are used with Cocoa Bindings, a _view model_ serves no purpose anymore. 67 | 68 | View models are often great for simplifying view controllers. Cocoa Bindings was designed to be a simplification itself. There's no use trying to combine both. 69 | 70 | Keeping the `TreeNode` protocol for now, it's simple to make `Box` and `Item` implement its contract: 71 | 72 | extension Box: TreeNode { 73 | public dynamic var children: [TreeNode] { 74 | return self.managedItems.map { $0 as! TreeNode } 75 | } 76 | 77 | public dynamic var isLeaf: Bool { return false } 78 | } 79 | 80 | extension Item: TreeNode { 81 | public dynamic var children: [TreeNode] { return [] } 82 | 83 | public dynamic var isLeaf: Bool { return true } 84 | } 85 | 86 | 87 | ### Super-Charging the View Controller 88 | 89 | A lot of delegates were just removed from the project. The functionality moves into the view controller. 90 | 91 | #### Rewiring the Controller 92 | 93 | The `NSTreeController` currently uses custom `TreeNode` objects. But it can be customized to use Core Data entities as well. The view controller exposes a `NSManagedObjectContext` from the view controller as the data source for this. I called it `managedObjectContext`; the property name will be important to establish Cocoa Bindings in a second. 94 | 95 | Then change the tree controller's settings in Interface Builder: 96 | 97 | * Under its Bindings settings, in the "Parameters" group at the bottom, set the Managed Object Context to point to the view controller. The model key path should read `self.managedObjectContext`. 98 | * Under its Attribute settings pane, change the "Mode" to "Entity Name" and set it to "ManagedBox" (which is the name of the entity in Core Data, not the name of the class we use). 99 | * Still looking at the Attribute pane, check "Prepares Content". 100 | 101 | This should make the tree controller fetch all instances at the beginning. That's why we don't need all these methods in `BoxRepository` anymore. 102 | 103 | 104 | #### Adding Boxes 105 | 106 | To add a box was pretty easy in the past: trigger an insertion event and consume the resulting `BoxData`, if any. 107 | 108 | Without this round-trip, we have to trigger a change of the tree controller's underlying data. 109 | 110 | Moving the `BoxRepository` sequence into the view controller, `addBox(_:)` now becomes: 111 | 112 | @IBAction open func addBox(_ sender: AnyObject) { 113 | guard let repository = repository else { return } 114 | 115 | let boxId = repository.nextId() 116 | repository.addBoxWithId(boxId, title: "NEW BOX!!") 117 | 118 | refreshTree() 119 | } 120 | 121 | In theory, we could bind the "Add Box" button to the tree controller directly; in practice, this doesn't work if there's more than one kind of entity to be managed. So if your app only has one kind of entity and nodes in the outline view, you can get rid of this, too. 122 | 123 | #### Adding Items 124 | 125 | To add an item is quite similar to adding a box. We still have to find the parent box. It's either the selected node itself, or the parent of the selected node. 126 | 127 | Since our nodes are not just dumb nodes anymore, each `Item` actually knows its parent `Box`. That's pretty convenient, and we can get rid of a lot of methods which helped discover the correct parent object. 128 | 129 | Instead of a handful, we end up with two methods: 130 | 131 | @IBAction open func addItem(_ sender: AnyObject) { 132 | guard hasSelection() else { return } 133 | 134 | addItemToSelectedBox() 135 | refreshTree() 136 | } 137 | 138 | func addItemToSelectedBox() { 139 | guard let repository = repository, 140 | let selectedNode = itemsController.selectedNodes.first 141 | else { return } 142 | 143 | let itemId = repository.nextItemId() 144 | 145 | if let box = selectedNode.representedObject as? Box { 146 | box.addItemWithId(itemId, title: "New Item") 147 | } else if let item = selectedNode.representedObject as? Item { 148 | item.box.addItemWithId(itemId, title: "New Sibling Item") 149 | } 150 | } 151 | 152 | That's a lot leaner. I appreciate that we lose a lot of code that got on my nerves in the past. This is not complicated at all. Because all the complexity is actually implemented in the frameworks where I can't see it. 153 | 154 | Less code is not much of an achievement in itself. It's a vanity metric. If your app is simple, you can get by with this approach very well since you wouldn't benefit from the purity of the past chapters. "Less code" should not be the guiding principle, though. "Enough code to do the job" is better; it forces you to think about what "the job" is. I know that my app, the Word Counter, has a more complex Domain Model, for example. The rest of the app has to be _sufficiently_ complex to deal with that, not more, so I don't compromise on readability and maintainability of the code base. 155 | 156 | #### Removing Nodes 157 | 158 | In a perfect world, we would've achieved Zen using this method: 159 | 160 | @IBAction func removeSelectedObject(sender: AnyObject) { 161 | guard hasSelection() else { return } 162 | 163 | itemsController.remove(sender) 164 | } 165 | 166 | Since `Box` is not KVC compliant for the `children` array, which is nothing more but a wrapper around its `managedItems: NSSet`, `remove(_:)` will raise a runtime error. 167 | 168 | This might be solved using not a computed `children` property but a real writeable collection. Either I add a KVC compliant wrapper around its `managedItems`, or I rename the Core Data property just for the sake of saving a few lines during removal. Both do not appeal to me. 169 | 170 | The view controller instead has to use the `BoxRepository` just like all the other mutating button callbacks to change the Core Data store contents. 171 | 172 | @IBAction open func removeSelectedObject(_ sender: AnyObject) { 173 | guard let repository = repository, 174 | hasSelection() 175 | else { return } 176 | 177 | let selectedNode = itemsController.selectedNodes.first! 178 | 179 | if let box = selectedNode.representedObject as? Box { 180 | repository.removeBox(boxId: box.boxId) 181 | } else if let item = selectedNode.representedObject as? Item { 182 | let itemId = item.itemId 183 | let box = item.box 184 | box.removeItem(itemId: itemId) 185 | } 186 | 187 | refreshTree() 188 | } 189 | 190 | The view controller file is down to a refreshing 130 lines of code. That's not much. 191 | 192 | #### Getting Rid of the Repository? 193 | 194 | `ItemViewController` will provide the `NSManagedObjectContext` for the tree controller. It has to come from somewhere; either through the `BoxRepository` or through the `ServiceLocator` or through injection via property. (I picked the `ServiceLocator` route.) 195 | 196 | Since the `NSManagedObjectContext` is known in the view controller, couldn't we use `Box.insertBoxWithId(_:, title:, intoManagedObjectContext:)` directly and get rid of the repository? 197 | 198 | In the end, there'd be just a few objects left: the use case type `ShowItemManagementWindow`, the `PersistentStack` to set up Core Data, the entities themselves, and the view components. Plus the obligatory `AppDelegate`, of course. In fact, if you create a new Xcode project with a Core Data template, you'll end in a similar position when you extract the `PersistentStack` early. 199 | 200 | That move would work. This example app is simple and the view controller code is short. That's usually not the case. Even if in this particular situation it might be okay to combine `BoxRepository` and `ItemViewController` in an attempt to get rid of all the old stuff, what would this teach us? To cram everything into view controllers? That's the bad situation we end up in sooner or later anyway. No need to push that and degrade the code further. 201 | 202 | Apple's documentation and example apps are great to show how to implement something; but they never show how to integrate that in your apps. Or how to scale the example code so you can build your idea on top of it. As soon as we cram the `BoxRepository` into the view controller, we won't know what to do when we add a second view component that needs to access `Box`es except to copy & paste existing code. Then maybe refactor to de-duplicate the code base. But maybe not, because we're in a hurry. That's how projects turn into legacy code. 203 | -------------------------------------------------------------------------------- /manuscript/part0-intro/600-patterns.txt: -------------------------------------------------------------------------------- 1 | ## Patterns & Principles 2 | 3 | Now you know the architectural perspective and a lot of _Domain-Driven Design_ modelling vocabulary. 4 | 5 | If the architecture is the high-level view, and the actual code is at the ground-level, there's plenty of room for abstractions and concepts in between. Design patterns and programming principles are the tools you will use to work according to the plan you devised with the help of your app's architecture. 6 | 7 | Throughout this book, you will find references to important patterns I have adopted to help me face particular challenges. Here are a few general but important ones. 8 | 9 | It makes sense to read these first to tune in to the way of thinking I advocate in this book. You don't have to read this chapter in order to understand the actual story of building the sample application, though. 10 | 11 | 12 | ### Thinking in Terms of Ports & Adapters and Protocols {#portsadapters} 13 | 14 | When we talked about architecture earlier, I mentioned that the application as a whole is open for adapters through its ports. The application doesn't incorporate a database management system, but it connects to it, preferably at the outermost layer to keep the rest isolated. 15 | 16 | Practicing to think in terms of ports and adapters can be weird at first, but it can pay off during object design. You design ports to be open for external adapters; and you create adapters according to port protocols that can be plugged into these ports. 17 | 18 | You're probably familiar with [the delegate pattern Apple advocates](https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html) throughout the Cocoa APIs. In effect, this is the same as the general advice to [program to an interface](http://www.artima.com/lejava/articles/designprinciples.html). It also is similar to the overall approach of ports and adapters. A delegate's protocol is similar to the definition of a port. The implementation and instantiation of a delegate as an object is similar to the notion of an adapter. 19 | 20 | If you know how the delegate pattern works, you know how ports and adapters work. In practice, adopting a ports and adapters style to design components means you end up with lots of protocol definitions. Swift encourages heavy use of protocols, so this change is very easy to implement nowadays. Objective-C can power a similar approach, but defining and using protocols is more cumbersome. 21 | 22 | ![Layered architecture with ports and adapters](images/20150723113926_layers-ports.png) 23 | 24 | Let's exercise the ports/adapters style a bit more. 25 | 26 | Take the core of your app, the Domain. The Domain should not access a database or Core Data directly so both Domain and database access are isolated from one another. The Domain should define a port which it queries for information accordings to its own internal terms; a database component adapter can be plugged in and satisfy this protocol. 27 | 28 | I hinted at this in the last section already: The concrete database adapter will be implemented in a layer farthest away from the core Domain. The outermost Infrastructure layer is the best candidate we know to wrap database access in objects you own and implement the Domain's protocol. 29 | 30 | In the picture above, `BoxView` is a protocol which defines how to `display(_:)` data. A concrete view controller implements this protocol and will be handed to the use case or a comparable service object. Similarly, `BoxRepository` is a protocol of the Domain implemented by a `CoreDataBoxRepository` in infrastructure. 31 | 32 | Decoupling objects in different layers from another through protocols is a great way to separate responsibilities. The user's course of action has to start somewhere, but this point in your app needn't be a view controller at all. The user interface is just another implementation detail. 33 | 34 | View controllers have to worry about displaying data and reacting to user interface interaction, most likely through `@IBAction` methods. The response can be delegated to use case objects, for example, which may or may not communicate back to the view controller -- they wouldn't know for sure, since they only know about the protocols. 35 | 36 | Protocols help partition Swift code nicely, too. It has become idiomatic to extend a type with protocols instead of adding (most) protocols to the initial inheritance list. In the end, it is a matter of taste, though. 37 | 38 | So instead of this: 39 | 40 | {linenos=off} 41 | class Pizza: Edible, ContainsGluten, ContainsRefinedCarbs { 42 | 43 | init() { ... } 44 | 45 | func eatenBy(eater: Eater) { ... } 46 | func allergicReaction(eater: Eater) -> AllergicReaction { ... } 47 | func makeSleepy(eater: Eater) { ... } 48 | } 49 | 50 | You'll rather write this: 51 | 52 | {linenos=off} 53 | class Pizza { 54 | init() { ... } 55 | } 56 | 57 | extension Pizza: Edible { 58 | func eatenBy(eater: Eater) { ... } 59 | } 60 | 61 | extension Pizza: ContainsGluten { 62 | func allergicReaction(eater: Eater) -> AllergicReaction { ... } 63 | } 64 | 65 | extension Pizza: ContainsRefinedCarbs { 66 | func makeSleepy(eater: Eater) { ... } 67 | } 68 | 69 | Extensions are great for reasoning about your code. 70 | 71 | Each extension contains everything needed to implement a particular protocol (except stored properties, which have to be declared in the root type definition). If you consider these extensions to be mixins of new behavior, the resulting type may not have a single purpose anymore. It has many responsibilities instead. If a type has too many responsibilities through many extensions, it may be better to make a new type out of it. This is an "Extract Class" refactoring: instead of `extension Foo: Bar` you create a new type through `class Baz: Bar` and delegate a `Baz` instance from a `Foo` instance. 72 | 73 | Partitioning your code through extensions makes it easier to refactor one type into multiple types. Each extension block shows which methods belongs to what protocol. Through showing the responsibilities, extension blocks can help to refactor existing types into multiple sub-types. 74 | 75 | ### Model--View--View Model (MVVM) 76 | 77 | While ports and adapters style component design is pretty high level still, MVVM is pretty close to the metal in terms of making view programming manageable. 78 | 79 | There's your core _domain model_. Around it, you build an application with its interface. The user interface is called _view_. So far, so similar to MVC. 80 | 81 | Now an MVC controller will usually have access to the model data and the view, or the view will be bound to the model and update itself automatically on model changes. The latter is sometimes called _smart view pattern_. As we'll see, I don't like either of that. A basic Domain-Driven Design principle I adhere to is this: domain objects should not bleed into other layers of the application. At least not if they're mutable, that is, if they're Aggregates. You can argue that read-only entities would do no harm, but I yet have to come across something like this. 82 | 83 | To get any data into the view, MVVM introduces the _view model_ or [_presentation model_][presmodel]. Applications of this pattern vary, but they all have in common that the view model is a container for the data of the domain model. The domain model is thus shielded from the user interface. I came to think that the view model is "a model for the view", but people repeatedly say it's a "model of the view" instead. Depending on your interpretation, how you apply the pattern will vary of course. 84 | 85 | * Dumb Data-Transfer Objects are "a model for the view". The view takes the data, works with it, and displays it somehow. These objects couple with the presenter or whichever service object puts data into the view. 86 | * Presenters, however, model what the view can do. They become "a model of the view." They take care of assembling data suited to the view's needs. These objects couple with the view and its demands. 87 | 88 | In the Ruby on Rails world, the presenter has become a service object which holds on to the domain model and exposes its data in a way meaningful to the view. Say you've got a `Person` entity with `firstName` and `lastName`. A `PersonPresenter` will take care of assembling a `fullName`, to display a list of `Person`s, for example. You can go all nuts here: create a `PersonListPresenter` for displaying full names in a list, and a `PersonFormPresenter` for exposing both first and last name to edit them in a form, say. This way, the view can be as dumb as possible. That's a case of preparing "a model for the view." After all, you pass the presenter in, so it becomes the view's model. 89 | 90 | I learned to think about it from a different perspective. 91 | 92 | ![MVVM with components taken from VIPER, which we'll get to know in Part 2.](images/201507231210_mvvm.png) 93 | 94 | Adhering to dependency inversion, the view model is indeed injected into the view. Its interface is specified by means of the view component arrangement, though -- not through a formal protocol, but through high coupling. 95 | 96 | If the view displays the full name of a person, the presenter's interface will codify this requirement as `fullName`, even if the original data did only contain `firstName` and `lastName`. 97 | 98 | Similarly, if the age of a person in represented through `Int`s but displayed in a label, the view model should expose `age: String` instead, so the view has to do _zero_ formatting at best. 99 | 100 | This way, the view is responsible for specifying its data provider's capabilities while it depends on some other service to provide an actual implementation. 101 | 102 | Your client code doesn't need to know what the view does or which components it's made of. It only needs to understand the presenter's interface. The presenter in this case is a model _of_ the view: it doesn't care about the Domain Model. It only cares about the view's specification. 103 | 104 | Change the properties on this _model of the view_ to update the UI. A presenter hides the complexity of the user interface. To the client code, the rest happens magically. 105 | 106 | MVVM is a good fit to model user interface changes and pass data around the different layers in an application. Opposed to the layered architecture we employ here, MVVM is not a software architecture; it's a design pattern, just like MVC, or the concepts of singletons, factories, builders, observers, or all the others from the Gang of Four book "Design Patterns". [Read more](http://cleancocoa.com/posts/2016/10/mvvms-place/) about my analysis of MVVM on my Clean Cocoa blog. 107 | 108 | [presmodel]: http://martinfowler.com/eaaDev/PresentationModel.html 109 | 110 | ### East-Oriented Code {#east} 111 | 112 | 113 | 114 | You may have heard of the ["Tell, Don't Ask" Principle](https://pragprog.com/articles/tell-dont-ask). Rephrased, it means you should focus on _commands_ and get rid of as much information _queries_ as possible. (We will encounter Command--Query Separation in various forms throughout this book.) 115 | 116 | James Ladd (@jamesladd) coined the phrase "East-Oriented Code". Where "Tell, Don't Ask" opens up to ambiguity at first, the principle of "East-Oriented Code" doesn't. It says that all your information should travel eastward, to the right, that is, no information should flow to the _left_ as you read your code.[^east] 117 | 118 | [^east]: James has posts on the topic on [his blog](http://jamesladdcode.com/category/its-all-about-writing-software/). So has Stephen Haberman written [about writer objects instead of getters](http://www.draconianoverlord.com/2013/04/12/east-oriented-programming.html). You may want to watch [a Ruby video](http://confreaks.com/videos/4825-RubyConf2014-eastward-ho-a-clear-path-through-ruby-with-oo) on the topic for good examples. 119 | 120 | Take a look at an example of mixed-up information flow: 121 | 122 | {linenos=off} 123 | let fullName: String = person.fullName 124 | theDisplay.nameLabel!.text = fullName 125 | 126 | Here, `person.fullName` clearly is a query. Setting the text of the display's label is bound to a query, too. For the dramatic effect, I made it so that we even have to unwrap the optional `nameLabel`. Setting the text is actually a command. But `theDisplay.nameLabel!` is a query, too. And a complicated one, even. 127 | 128 | [Sandi Metz](http://www.sandimetz.com/blog/2014/12/19/suspicions-of-nil) summarizes the problem of optionals (or `nil` objects in other languages than Swift) like this: `nil` makes you return "objects which conform to different API's". 129 | 130 | In Ruby, this is even more apparent, because a `nil` object actually responds to a few methods. In Objective-C, it responds to everything and fails silently. In Swift, we have to deal with runtime errors. It's more verbose, and I think it's a good idea to not allow to return `nil` whenever possible because we have to deal with two types of things instead of one. 131 | 132 | Making your code "East-Oriented" means you should at least get rid of queries. Don't ask for information or view components -- tell your objects to perform changes instead: 133 | 134 | {linenos=off} 135 | person.displayName(theDisplay) 136 | 137 | Now `person` may hide its `fullName` from the outside, because you shouldn't query it anyway. Internally, it could do this: 138 | 139 | {linenos=off} 140 | func displayName(aDisplay: DisplaysNames) -> Person { 141 | let data = ["firstName" : firstName, "lastName" : lastName] 142 | aDisplay.displayName(data) 143 | 144 | return self 145 | } 146 | 147 | The `DisplaysNames` protocol can specify alternate `displayName` methods which accepts more than a single data parameter instead. But then `Person` would have to know about this, and passing initials or middle names will require different methods instead of different keys in the data container. I like the flexibility of this approach to illustrate the principle. 148 | 149 | Also worth noting is that I suddenly return the `Person` object's instance. Shouldn't a command be of return type `Void`? 150 | 151 | According to one accompanying convention of the "East-Oriented Code" principle, we should `return self` in commands in order to chain them: 152 | 153 | {linenos=off} 154 | person.displayName(theDisplay) 155 | .storeShoppingCart(theCartHandler) 156 | .issueRefunds() 157 | 158 | [James' principles](http://jamesladdcode.com/?p=302) in a nutshell: 159 | 160 | > 1. All **public** methods should be void, boolean or return a reference to the current object (this in Java). 161 | > 2. Objects that implement the Factory or Builder pattern or similar are an exception. 162 | > 3. East is better suited to composite objects, not primitive objects. 163 | 164 | You don't have to make your code chainable, of course. If returning `self` causes problems or breaks your tests, though, your code isn't ready for focussing on a command-centric approach. This should become a driver of change towards useful refactorings. 165 | 166 | Although "Tell, Don't Ask" can be superseded by functional programming paradigms, we will stick to these more traditional object-oriented design approaches throughout the book. I will apply a lot of this later, for example when it comes to [using commands instead of queries](#refactoring-to-commands) to provision new `Box` objects in the Domain. 167 | -------------------------------------------------------------------------------- /manuscript/part2-process/200-naive add remove.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Adding `Item`s as Part of Their Larger Aggregate `Box` 4 | 5 | Adding items takes a bit more lines to test, but it's nothing special: 6 | 7 | func testAddItem_WithBoxInRepo_CreatesItemBelowBox() { 8 | let existingId = BoxId(1337) 9 | ManagedBox.insertManagedBox(existingId, 10 | title: "irrelevant", 11 | inManagedObjectContext: context) 12 | XCTAssertEqual(boxRepository!.count(), 1, "repo contains a box") 13 | 14 | // When 15 | viewController!.addItem(self) 16 | 17 | // Then 18 | let box = allBoxes().first 19 | XCTAssert(hasValue(box)) 20 | if let box = box { 21 | XCTAssertEqual(box.items.count, 1, "contains an item") 22 | 23 | if let item: ManagedItem = box.items.anyObject() as? ManagedItem { 24 | XCTAssertEqual(item.title, "New Item") 25 | XCTAssertEqual(item.box, box) 26 | } 27 | } 28 | } 29 | 30 | 31 | {pagebreak} 32 | 33 | Appending `ItemNode` to the current selection (or its parent) `BoxNode` works by finding the `BoxNode`'s index path. The associated `NSTreeNode` provides all details to obtain the `BoxId`. 34 | 35 | {title="Appending item nodes"} 36 | func appendItemNodeToBoxIndexPath(parentIndexPath: IndexPath) { 37 | let parentTreeNode = boxTreeNodeAtIndexPath(parentIndexPath) 38 | let itemIndexPath = indexPath(appendedToTreeNode: parentTreeNode) 39 | let item = itemNode(belowBoxTreeNode: parentTreeNode) 40 | 41 | itemsController.insertObject(item, 42 | atArrangedObjectIndexPath: itemIndexPath) 43 | } 44 | 45 | // ... 46 | 47 | func itemNode(belowBoxTreeNode boxTreeNode: NSTreeNode)-> ItemNode { 48 | let boxNode = boxTreeNode.representedObject as! BoxNode 49 | let itemId = eventHandler.provisionNewItemId(inBox: boxNode.boxId) 50 | 51 | return ItemNode(itemId: itemId) 52 | } 53 | 54 | Now that `ItemViewController` asks for a new `ItemId`, we could simply replicate `provisionNewBoxId()` and ask the `CoreDataItemRepository` for a new ID, insert it, and all is well. 55 | 56 | {title="How BoxAndItemService should not provision item IDs"} 57 | public func provisionNewItemId(inBox boxId: BoxId) -> ItemId { 58 | let repository = ServiceLocator.itemRepository() 59 | let itemId = repository.nextId() 60 | let item = Item(itemId: ItemId, title: "New Item") 61 | 62 | repository.addItem(item) 63 | 64 | return itemId 65 | } 66 | 67 | Stop right there. This may be a mistake. 68 | 69 | #### Revisiting the Meaning of Aggregates in the Domain 70 | 71 | Think about the domain's specification. `Item`s are part of `Box`es. It doesn't make sense to create them in isolation. In fact, the code above wouldn't execute without errors because the Core Data `ManagedItem` definition declares the `box`-relationship must be set. 72 | 73 | In Domain-Driven Design terms, `Box` is an **Aggregate Root**. It is responsible for managing its invariants and related entities. `Item`s are related entities: they have identity over time and across sessions. But in this scenario, they aren't to be managed independently from their respective `Box`es. 74 | 75 | At the moment and with the current domain design in hand, it doesn't make sense to fetch `Item`s in isolation. That's not the case for the Word Counter. There, paths may belong to more than one project. It would make sense to fetch all paths and start file monitoring. In this example application, it doesn't make sense to fetch all `Item`s without their `Box`es. That's why I keep `Item` as a part of the `Box` aggregate. 76 | 77 | Creating `Item`s thus becomes part of the boundary of `Box`. 78 | 79 | {title="How BoxAndItemService should really provision item IDs"} 80 | public func provisionNewItemId(inBox boxId: BoxId) -> ItemId? { 81 | let repository = ServiceLocator.boxRepository() 82 | 83 | guard let box = repository.boxWithId(boxId) else { 84 | return nil 85 | } 86 | 87 | let itemId = repository.nextItemId() 88 | box.addItem(itemId, title: "New Item") 89 | 90 | return itemId 91 | } 92 | 93 | This aspect of the domain changes how to think about the box and item **Repositories**. 94 | 95 | Until now, I had collection-oriented repositories in mind.[^repo] You query the repository for objects, change them, and not worry about saving. The repository does it behind the scenes. If I worked with Core Data managed objects directly, that's exactly what would happen. But I don't, so I have to ensure that changes to a `Box` find their way to the Core Data store. 96 | 97 | There's three options to bind the `Box` Domain object to a Core Data managed object: 98 | 99 | 1. Change the way the Repositories work. Instead of collection-oriented, make them work persistence-oriented. This includes explicit calls to `save` methods, passing in `Box` or `Item`. Since I don't manipulate a SQL database directly, I have to map changes from `Box` and `Item`to `ManagedBox` and `ManagedItem` respectively. 100 | 2. Leverage KVO to track changes of `Box` objects and map changes to the underlying `ManagedBox`. Similar to (1), but without explicit calls to a `save` method. 101 | 3. Use `NSManagedObject` directly to manipulate the persisted data. Every change gets persisted automagically. 102 | 103 | I think (1) is a waste of time in this case. (2) is a viable option I'll explore more. Discovering these difficulties in practice, I will show you how (3) works in part 4 of this book. 104 | 105 | So from this point onward, I'll show you how you could sync `NSManagedObject` subclasses to Domain objects. It's not going to be super simple, but it'll work and illustrate the point of keeping these things separate. 106 | 107 | [^repo]: See Chapter 12, "Repositories", in [Vernon (2013).](#booklist) 108 | 109 | #### Tracking `Box` changes from within `ManagedBox` 110 | 111 | To leverage Key-Value Observing in Swift, I have to change `Box` to this: 112 | 113 | public class Box: NSObject { 114 | public let boxId: BoxId 115 | public dynamic var title: String // Enable KVO & changes 116 | 117 | public init(boxId: BoxId, title: String) { 118 | self.boxId = boxId 119 | self.title = title 120 | } 121 | } 122 | 123 | `ManagedBox` observation is set up in a lazy property initializer. This way, the `Box` can use `ManagedBox`es values when it's requested. Since the Core Data managed object context keeps fetched values around until saving, subsequent fetch requests will produce the same `ManagedBox` and thus the same `Box`: 124 | 125 | {title="Setting up KVO on a private Box representation"} 126 | private var boxContext = 0 127 | 128 | open class ManagedBox: NSManagedObject { 129 | // ... 130 | 131 | fileprivate var _box: Box? 132 | public lazy var box: Box = { 133 | 134 | let box = Box(boxId: self.boxId(), title: self.title) 135 | box.addObserver(self, 136 | forKeyPath: "title", 137 | options: .New, 138 | context: &boxContext) 139 | 140 | self._box = box 141 | return box 142 | }() 143 | 144 | open override func observeValue( 145 | forKeyPath keyPath: String?, 146 | of object: Any?, 147 | change: [NSKeyValueChangeKey : Any]?, 148 | context: UnsafeMutableRawPointer?) { 149 | 150 | guard context == &boxContext else { 151 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 152 | return 153 | } 154 | 155 | if keyPath == "title" { 156 | self.title = change?[.newKey] as! String 157 | } 158 | } 159 | 160 | deinit { 161 | if let box = _box { 162 | box.removeObserver(self, forKeyPath: "title") 163 | } 164 | } 165 | } 166 | 167 | {pagebreak} 168 | 169 | This works pretty well for fetched boxes: 170 | 171 | {linenos=off} 172 | public class CoreDataBoxRepository: BoxRepository { 173 | // ... 174 | public func boxWithId(boxId: BoxId) -> Box? { 175 | guard let managedBox = managedBoxWithUniqueId(boxId.identifier) 176 | else { return nil } 177 | 178 | return managedBox.box 179 | } 180 | } 181 | 182 | #### Populating the view with content from Core Data 183 | 184 | To add an `Item` to a `Box`, the view controller has to know about items in the data store. Until now, the view always starts blank. 185 | 186 | Showing the view and populating its data can be modeled as a use case: `ManageBoxesAndItems`. Another naming candidate is `ShowBoxManagementWindow` or similar to express the intent, but `ManageBoxesAndItems` better captures the user's intention. 187 | 188 | {title="Use-case based Application Service"} 189 | public class ManageBoxesAndItems { 190 | var windowController: ItemManagementWindowController! 191 | // ... 192 | 193 | public func showBoxManagementWindow() { 194 | let repository = ServiceLocator.boxRepository() 195 | let allBoxes = repository.boxes() 196 | let allBoxData = boxData(boxes: allBoxes) 197 | 198 | windowController.displayBoxData(allBoxData) 199 | 200 | showWindow() 201 | } 202 | 203 | func boxData(boxes: [Box]) -> [BoxData] { 204 | // Adapt each Box to BoxData, the view's Data Transfer Object 205 | } 206 | } 207 | 208 | `BoxData` is (yet) another simple struct to pass values to the view, and another chance to be thankful for Swift: 209 | 210 | {title="Data Transfer Objects"} 211 | public struct BoxData { 212 | let boxId: BoxId 213 | let title: String 214 | let itemData: [ItemData] 215 | } 216 | 217 | public struct ItemData { 218 | let itemId: ItemId 219 | let title: String 220 | } 221 | 222 | In Swift, I favor simple structs over dictionary representations of objects for similar reasons I favored 3 explicit `TreeNode` types over 1 general-purpose type. The compiler will help setting instances up properly. In Objective-C, on the other hand, I often hesitate to add yet another class to the code base. This is part of un-learning old habits if you come from Objective-C. 223 | 224 | The view controller `ItemViewController` now can create `BoxNode`s and `ItemNode`s in its `displayBoxData` based on the data provided. The window controller simply forwards the message. 225 | 226 | This simple use case-oriented **Application Service** object relieves the `AppDelegate` from a lot of responsibilities. To show the window at startup, it simply invokes the use case service: 227 | 228 | class AppDelegate: NSObject, NSApplicationDelegate { 229 | // ... 230 | lazy var manageBoxesAndItems = ManageBoxesAndItems() 231 | 232 | func applicationDidFinishLaunching(_ aNotification: Notification) { 233 | let moc = persistentStack.managedObjectContext! 234 | ServiceLocator.sharedInstance.setManagedObjectContext(moc) 235 | manageBoxesAndItems.showBoxManagementWindow() 236 | } 237 | } 238 | 239 | This works well in tests. And all of a sudden the application persists the user's changes -- at least when it comes to adding `Box`es.[^d8b64eabee134cd516b382956e2aa58c194d5886] 240 | 241 | 247 | 248 | Obtaining the `Box` via `ManagedBox` didn't re-create the associated items, yet. It's easily achieved via `map()`, though: 249 | 250 | let box = Box(boxId: self.boxId(), title: self.title) 251 | let managedItems = self.items.allObjects as! [ManagedItem] 252 | let items = managedItems.map() { (item: ManagedItem) -> Item in 253 | return item.item 254 | } 255 | box.items = items 256 | 257 | That's all there is to it to create, save, and show items with their default values. Changing the values has no lasting effect, yet, though. 258 | 259 | 260 | [^d8b64eabee134cd516b382956e2aa58c194d5886]: See [commits d8b64ea](https://github.com/CleanCocoa/mac-appdev-code/commit/d8b64eabee134cd516b382956e2aa58c194d5886), [b4c63ff](https://github.com/CleanCocoa/mac-appdev-code/commit/b4c63ff803672889f7683aae72554d9538c7f335), [6cfabcf](https://github.com/CleanCocoa/mac-appdev-code/commit/6cfabcf026b516b69a30873aa30ea68de79a6aa9), and [f9d96aa](https://github.com/CleanCocoa/mac-appdev-code/commit/f9d96aa1f19db681ba654f2bdf067bd8a7edf08f) 261 | 262 | {pagebreak} 263 | 264 | ### Renaming Boxes and Items 265 | 266 | There are two options I can think of to take note of changes to the view model, that is, `BoxNode` and `ItemNode` titles: 267 | 268 | 1. Leverage KVO in the view controller to notify the event handler, or 269 | 2. Use Swift property observers `willSet`/`didSet` to fire a message to any interested party. 270 | 271 | The first approach sounds good because there's a single event handler, using the already established delegate pattern. The second approach takes far less boilerplate code to set up, and I don't need to worry about removing the observer upon item removal. On the downside, I have to send messages using the notification center. Who should subscribe to these? The view controller or the event handler? 272 | 273 | In classic Model--View--Controller fashion,[^mvc] every view component has its own controller. One could say that the `didSet` property observer already is part of the controller. Why delegate it up to the list component's facade, `ItemViewController`? Thus the objective is to send a change message to the event handler from the `didSet` property observer directly. I could set a (shared) `eventHandler` property to every `TreeNode` upon initialization and invoke `boxDidChange(_:BoxId, title:String)`, for example. The consequences are severe, though: 274 | 275 | * `BoxNode` has to be representable in Objective-C, thus its `eventHandler` protocol has to be annotated `@objc`, too. 276 | * The protocol, `HandlesItemListChanges`, cannot be Objective-C compatible if `BoxId` isn't. 277 | * `BoxId` is a struct. It's a genuine value object. To make it Objective-C compatible, it has to become a class instead. 278 | 279 | I don't want to water-down the existing design just for the sake of convenience. Instead, I opt in to delegate `didSet` messages up to the `ItemViewController`. 280 | 281 | {title="Introduce a tree node-specific change observer"} 282 | public protocol HandlesItemListChanges: class { 283 | func treeNodeDidChange(treeNode: TreeNode, title: String) 284 | } 285 | 286 | public class BoxNode: NSObject, TreeNode { 287 | public dynamic var title: String = "New Box" { 288 | didSet { 289 | guard let controller = self.eventHandler 290 | else { return } 291 | 292 | controller.treeNodeDidChange(treeNode: self, title: title) 293 | } 294 | } 295 | 296 | public weak var eventHandler: HandlesItemListChanges? 297 | // ... 298 | } 299 | 300 | You can make use of this Model--View--View Model-based approach with any kind of components. You can bind strings to text fields, for example, without using the way too heavy Cocoa Bindings (which I find hard to set up in code). Property observers are really powerful. Srdan Rasic shared a really good implementation of an [all-purpose value object wrapper][rasic-mvvm] with MVVM implementation. 301 | 302 | [rasic-mvvm]: http://rasic.info/bindings-generics-swift-and-mvvm/ 303 | 304 | Until now, `BoxNode` did set up its own child nodes when it was reconstituted from `BoxData`. I move this part out into `ItemViewController` to set the `eventHandler` property to `self` there: 305 | 306 | {title="Add this to ItemViewController:"} 307 | public func displayBoxData(_ boxData: [BoxData]) { 308 | removeExistingNodes() 309 | for data in boxData { 310 | itemsController.addObject(boxNode(data)) 311 | } 312 | } 313 | 314 | func removeExistingNodes() { 315 | itemsController.content = NSMutableArray() 316 | } 317 | 318 | func boxNode(boxData: BoxData) -> BoxNode { 319 | let boxNode = BoxNode(boxData: boxData) 320 | boxNode.eventHandler = self 321 | boxNode.children = itemNodes(allItemData: boxData.itemData) 322 | 323 | return boxNode 324 | } 325 | 326 | func itemNodes(allItemData: [ItemData]) -> [ItemNode] { 327 | let result: [ItemNode] = allItemData.map() { itemData in 328 | self.itemNode(itemData) 329 | } 330 | return result 331 | } 332 | 333 | func itemNode(itemData: ItemData) -> ItemNode { 334 | let itemNode = ItemNode(itemData: itemData) 335 | itemNode.eventHandler = self 336 | 337 | return itemNode 338 | } 339 | 340 | Another option is to pipe the value for every node's `eventHandler` upon reconstitution, so the method signature becomes `BoxNode.init(_: BoxData, eventHandler: HandlesItemListChanges?)`. 341 | 342 | The Application Service still is pretty simple: 343 | 344 | public class BoxAndItemService: HandlesItemListEvents { 345 | // ... 346 | 347 | public func boxDidChange(boxId: BoxId, title: String) { 348 | let repository = ServiceLocator.boxRepository() 349 | 350 | guard let box = repository.boxWithId(boxId) 351 | else { return } 352 | 353 | box.title = title 354 | } 355 | } 356 | 357 | Does phrasing it this way make sense, though? 358 | 359 | Just try read it: "Box and item service handles item list events. Box did change to a new title. Set box title to the new title." When the box changed to a new title, change the title? Really? 360 | 361 | The real `Box` _should_ change. The user-facing component _did_ change. 362 | 363 | How would you phrase it? 364 | 365 | I decide to use an active form for this method. Similar to `provisionNewBoxId`, this method will be called `changeBoxTitle`. This diverges from Cocoa naming conventions, but it makes more sense. An overall observation when looking at real _Domain-Driven Design_ project code is that the code reads well. I hope you agree that this is more important than adhering to naming conventions, especially if you don't expose a public API.[^f4abec848fdd107fff3d4badeaa83877bfaf930e] 366 | 367 | 368 | 369 | Similarly, I want `changeItemTitle` to look like this: 370 | 371 | public func changeItemTitle( 372 | itemId: ItemId, 373 | title: String, 374 | inBox boxId: BoxId) { 375 | 376 | let repository = ServiceLocator.boxRepository() 377 | 378 | guard let box = repository.box(boxId: boxId), 379 | let item = box.item(itemId: itemId) 380 | else { return } 381 | 382 | item.title = title 383 | 384 | } 385 | 386 | To obtain the parent item's `BoxId`, I walk the tree controller's arranged objects to generate a collection of `BoxNode`s. Then, I let the `ItemNode` decide where it belongs: 387 | 388 | public func parentBoxNode(inArray nodes: [BoxNode]) -> BoxNode? { 389 | for boxNode in nodes { 390 | if (boxNode.children as! [ItemNode]).contains(self) { 391 | return boxNode 392 | } 393 | } 394 | 395 | return nil 396 | } 397 | 398 | With these changes in place, the item title change test passes, too. 399 | 400 | [^mvc]: Like, 1988-classic. See [Wikipedia's MVC history](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller#History) for links to articles on the topic. 401 | 402 | [^f4abec848fdd107fff3d4badeaa83877bfaf930e]: See [commit f4abec8](https://github.com/CleanCocoa/mac-appdev-code/commit/f4abec848fdd107fff3d4badeaa83877bfaf930e) 403 | 404 | 405 | ### Removing Items and Boxes 406 | 407 | Adding this is pretty straight-forward now that changing is in place already. The interface I aim for is this: 408 | 409 | {linenos=off} 410 | func removeBox(boxId: BoxId) 411 | func removeItem(itemId: ItemId, fromBox boxId: BoxId) 412 | 413 | The tests have to verify the following: 414 | 415 | * Removing an `ItemNode` removes the `ManagedItem` from its `ManagedBox` 416 | * Removing a `BoxNode` removes the `ManagedBox` 417 | * Removing a `BoxNode` removes its `ManagedItems` with it 418 | 419 | There's nothing new to do to accomplish what's left. I like to show you the Application Service's implementation because I think it conveys best how well the parts integrate: 420 | 421 | public class BoxAndItemService: HandlesItemListEvents { 422 | var repository: BoxRepository { 423 | return ServiceLocator.boxRepository() 424 | } 425 | 426 | public func removeBox(boxId: BoxId) { 427 | repository.removeBox(boxId: boxId) 428 | } 429 | 430 | public func removeItem(itemId: ItemId, fromBox boxId: BoxId) { 431 | guard let box = repository.box(boxId: boxId) 432 | else { return } 433 | 434 | box.removeItem(itemId: itemId) 435 | } 436 | // ... 437 | } 438 | 439 | Clean code often sports succinct method bodies. These methods are pretty short and I think they read well. To me, reading them is feedback in itself: the services seem to play together nicely. 440 | --------------------------------------------------------------------------------