{"id":3522,"date":"2024-05-28T23:49:24","date_gmt":"2024-05-28T20:49:24","guid":{"rendered":"https:\/\/demensdeum.com\/blog\/?p=3522"},"modified":"2024-12-16T22:32:13","modified_gmt":"2024-12-16T19:32:13","slug":"crud-repository","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/pt\/2024\/05\/28\/crud-repository\/","title":{"rendered":"Reposit\u00f3rio CRUD"},"content":{"rendered":"<p>Nesta nota descreverei os princ\u00edpios b\u00e1sicos do conhecido padr\u00e3o cl\u00e1ssico CRUD, implementado na linguagem Swift. Swift \u00e9 uma linguagem aberta e multiplataforma dispon\u00edvel para Windows, Linux, macOS, iOS, Android.<\/p>\n<p>Existem muitas solu\u00e7\u00f5es para abstrair o armazenamento de dados e a l\u00f3gica do aplicativo. Uma dessas solu\u00e7\u00f5es \u00e9 a abordagem CRUD, que \u00e9 um acr\u00f4nimo para C&#8211; Criar, R -Leia, U&#8211; Atualiza\u00e7\u00e3o, D&#8211; Excluir.<br \/>Normalmente, este princ\u00edpio \u00e9 implementado atrav\u00e9s da implementa\u00e7\u00e3o de uma interface para o banco de dados, na qual os elementos s\u00e3o manipulados usando um identificador \u00fanico, como o id. Uma interface \u00e9 criada para cada letra CRUD &#8211; Criar(objeto, id), Ler(id), Atualizar(objeto, id), Excluir(objeto, id).<br \/>Se um objeto cont\u00e9m um id dentro de si, ent\u00e3o o argumento id pode ser omitido dos m\u00e9todos (Create, Update, Delete), j\u00e1 que todo o objeto \u00e9 passado para l\u00e1 junto com seu campo &#8211; eu ia. Mas para &#8211; A leitura requer id porque queremos obter um objeto do banco de dados por id.<\/p>\n<p>Todos os nomes s\u00e3o fict\u00edcios<\/p>\n<p>Vamos imaginar que um hipot\u00e9tico aplicativo AssistantAI foi criado usando o SDK gratuito do banco de dados EtherRelm, a integra\u00e7\u00e3o era simples, a API era muito conveniente e, como resultado, o aplicativo foi lan\u00e7ado nos mercados.<br \/>De repente, o desenvolvedor do SDK EtherRelm decide torn\u00e1-lo pago, fixando o pre\u00e7o em US$ 100 por ano por usu\u00e1rio do aplicativo.<br \/>O que? Sim! O que os desenvolvedores da AssistantAI devem fazer agora, pois j\u00e1 possuem 1 milh\u00e3o de usu\u00e1rios ativos! Pagar US$ 100 milh\u00f5es?<br \/>Em vez disso, \u00e9 tomada a decis\u00e3o de avaliar a transfer\u00eancia da aplica\u00e7\u00e3o para o banco de dados RootData nativo da plataforma, segundo os programadores, tal transfer\u00eancia levar\u00e1 cerca de seis meses, isso n\u00e3o leva em considera\u00e7\u00e3o a implementa\u00e7\u00e3o de novos recursos na aplica\u00e7\u00e3o; Depois de pensar um pouco, foi tomada a decis\u00e3o de remover o aplicativo dos mercados, reescrev\u00ea-lo em outro framework multiplataforma gratuito com um banco de dados BueMS integrado, isso resolver\u00e1 o problema com o banco de dados pago + simplificar\u00e1 o desenvolvimento em outras plataformas. <br \/>Um ano depois, o aplicativo foi reescrito em BueMS, mas de repente o desenvolvedor do framework decide torn\u00e1-lo pago. Acontece que a equipe caiu na mesma armadilha duas vezes. Se conseguir\u00e3o sair na segunda vez \u00e9 uma hist\u00f3ria completamente diferente.<\/p>\n<p>Abstra\u00e7\u00e3o para o resgate<\/p>\n<p>Esses problemas poderiam ter sido evitados se os desenvolvedores tivessem usado uma abstra\u00e7\u00e3o de interfaces dentro da aplica\u00e7\u00e3o. Aos tr\u00eas pilares da OOP &#8211; polimorfismo, encapsulamento, heran\u00e7a, n\u00e3o faz muito tempo eles adicionaram outro &#8211; abstra\u00e7\u00e3o.<br \/>A abstra\u00e7\u00e3o de dados permite descrever ideias e modelos em termos gerais, com um m\u00ednimo de detalhes, ao mesmo tempo em que \u00e9 preciso o suficiente para implementar implementa\u00e7\u00f5es espec\u00edficas usadas para resolver problemas de neg\u00f3cios.<br \/>Como podemos abstrair a opera\u00e7\u00e3o do banco de dados para que a l\u00f3gica da aplica\u00e7\u00e3o n\u00e3o dependa dela? Usamos a abordagem CRUD!<\/p>\n<p>Um diagrama UML CRUD simplificado tem esta apar\u00eancia:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/05\/image3.png\" alt=\"\" width=\"263\" height=\"118\" class=\"alignnone size-full wp-image-3537\" \/><\/p>\n<p>Exemplo com um banco de dados EtherRelm fict\u00edcio:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/05\/image2.png\" alt=\"\" width=\"272\" height=\"118\" class=\"alignnone size-full wp-image-3538\" \/><\/p>\n<p>Exemplo com um banco de dados SQLite real:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2024\/05\/image1.png\" alt=\"\" width=\"243\" height=\"118\" class=\"alignnone size-full wp-image-3539\" \/><\/p>\n<p>Como voc\u00ea j\u00e1 percebeu, ao trocar o banco de dados, apenas ele muda a interface CRUD com a qual a aplica\u00e7\u00e3o interage permanece inalterada. CRUD \u00e9 uma implementa\u00e7\u00e3o do padr\u00e3o GoF &#8211; Adaptador, porque com ele adaptamos interfaces de aplica\u00e7\u00f5es a qualquer banco de dados e combinamos interfaces incompat\u00edveis.<br \/>As palavras est\u00e3o vazias, mostre-me o c\u00f3digo<br \/>Para implementar abstra\u00e7\u00f5es em linguagens de programa\u00e7\u00e3o, s\u00e3o usadas interfaces\/protocolos\/classes abstratas. Todos esses s\u00e3o fen\u00f4menos da mesma ordem, por\u00e9m, durante as entrevistas voc\u00ea pode ser solicitado a nomear a diferen\u00e7a entre eles, eu pessoalmente acho que isso n\u00e3o faz muito sentido porque o \u00fanico prop\u00f3sito de uso \u00e9 implementar a abstra\u00e7\u00e3o de dados, caso contr\u00e1rio \u00e9 para testar a mem\u00f3ria do entrevistado.<br \/>O CRUD \u00e9 frequentemente implementado dentro da estrutura do padr\u00e3o Repository, por\u00e9m, o reposit\u00f3rio pode ou n\u00e3o implementar a interface CRUD, tudo depende da engenhosidade do desenvolvedor.<\/p>\n<p>Considere um c\u00f3digo Swift bastante t\u00edpico para o reposit\u00f3rio da estrutura Book, trabalhando diretamente com o banco de dados UserDefaults:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nstruct Book: Codable {\n\tlet title: String\n\tlet author: String\n}\n\nclass BookRepository {\n\tfunc save(book: Book) {\n    \t\tlet record = try! JSONEncoder().encode(book)\n    \t\tUserDefaults.standard.set(record, forKey: book.title)\n\t}\n    \n\tfunc get(bookWithTitle title: String) -&gt; Book? {\n    \t\tguard let data = UserDefaults.standard.data(forKey: title) else { return nil }\n    \t\tlet book = try! JSONDecoder().decode(Book.self, from: data)\n    \t\treturn book\n\t}\n    \n\tfunc delete(book: Book) {\n    \t\tUserDefaults.standard.removeObject(forKey: book.title)\n\t}\n}\n\nlet book = Book(title: \"Fear and Loathing in COBOL\", author: \"Sir Edsger ZX Spectrum\")\nlet repository = BookRepository()\nrepository.save(book: book)\nprint(repository.get(bookWithTitle: book.title)!)\nrepository.delete(book: book)\nguard repository.get(bookWithTitle: book.title) == nil else {\n\tprint(\"Error: can't delete Book from repository!\")\n\texit(1)\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>O c\u00f3digo acima parece simples, mas vamos contar o n\u00famero de viola\u00e7\u00f5es do princ\u00edpio DRY (Do not Repeat Yourself) e a coer\u00eancia do c\u00f3digo:<br \/>Conectividade com o banco de dados UserDefaults<br \/>Conectividade com codificadores e decodificadores JSON &#8211; JSONEncoder, JSONDecoder<br \/>Conectado com a estrutura Book, mas precisamos de um reposit\u00f3rio abstrato para n\u00e3o criar uma classe de reposit\u00f3rio para cada estrutura que iremos armazenar no banco de dados (viola\u00e7\u00e3o DRY)<\/p>\n<p>Vejo esse tipo de c\u00f3digo de reposit\u00f3rio CRUD com bastante frequ\u00eancia, ele pode ser usado, mas o alto acoplamento e a duplica\u00e7\u00e3o de c\u00f3digo levam ao fato de que, com o tempo, seu suporte se tornar\u00e1 muito complicado. Isso ser\u00e1 especialmente percept\u00edvel ao tentar mudar para outro banco de dados, ou ao alterar a l\u00f3gica interna de trabalho com o banco de dados em todos os reposit\u00f3rios criados na aplica\u00e7\u00e3o.<br \/>Em vez de duplicar o c\u00f3digo, mantenha o acoplamento alto &#8211; Vamos escrever um protocolo para o reposit\u00f3rio CRUD, abstraindo assim a interface do banco de dados e a l\u00f3gica de neg\u00f3cio da aplica\u00e7\u00e3o, respeitando o DRY, implementando baixo acoplamento:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>    typealias Item = Codable\n    typealias ItemIdentifier = String\n    \n    func create&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws\n    func read&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier) async throws -&gt; T\n    func update&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws\n    func delete(id: CRUDRepository.ItemIdentifier) async throws\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>O protocolo CRUDRepository descreve interfaces e tipos de dados associados para implementa\u00e7\u00e3o adicional de um reposit\u00f3rio CRUD espec\u00edfico.<\/p>\n<p>A seguir escreveremos uma implementa\u00e7\u00e3o espec\u00edfica para o banco de dados UserDefaults:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>    private typealias RecordIdentifier = String\n    \n    let tableName: String\n    let dataTransformer: DataTransformer\n    \n    init(\n   \t tableName: String = \"\",\n   \t dataTransformer: DataTransformer = JSONDataTransformer()\n    ) {\n   \t self.tableName = tableName\n   \t self.dataTransformer = dataTransformer\n    }\n    \n    private func key(id: CRUDRepository.ItemIdentifier) -&gt; RecordIdentifier {\n   \t \"database_\\(tableName)_item_\\(id)\"\n    }\n   \t \n    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -&gt; Bool {\n   \t UserDefaults.standard.data(forKey: key(id: id)) != nil\n    }\n    \n    func create&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n   \t let data = try await dataTransformer.encode(item)\n   \t UserDefaults.standard.set(data, forKey: key(id: id))\n   \t UserDefaults.standard.synchronize()\n    }\n    \n    func read&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier) async throws -&gt; T {\n   \t guard let data = UserDefaults.standard.data(forKey: key(id: id)) else {\n   \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t }\n   \t let item: T = try await dataTransformer.decode(data: data)\n   \t return item\n    }\n    \n    func update&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n   \t guard try await isExists(id: id) else {\n   \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t }\n   \t let data = try await dataTransformer.encode(item)\n   \t UserDefaults.standard.set(data, forKey: key(id: id))\n   \t UserDefaults.standard.synchronize()\n    }\n    \n    func delete(id: CRUDRepository.ItemIdentifier) async throws {\n   \t guard try await isExists(id: id) else {\n   \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t }\n   \t UserDefaults.standard.removeObject(forKey: key(id: id))\n   \t UserDefaults.standard.synchronize()\n    }\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>O c\u00f3digo parece longo, mas cont\u00e9m uma implementa\u00e7\u00e3o concreta completa de um reposit\u00f3rio CRUD contendo acoplamento fraco, detalhes abaixo.<br \/>typealias foram adicionadas para autodocumenta\u00e7\u00e3o do c\u00f3digo.<br \/>Acoplamento fraco e acoplamento forte<br \/>O descolamento de uma estrutura espec\u00edfica (struct) \u00e9 implementado usando o T gen\u00e9rico, que por sua vez deve implementar os protocolos Codable. Codable permite converter estruturas usando classes que implementam os protocolos TopLevelEncoder e TopLevelDecoder, por exemplo JSONEncoder e JSONDecoder, ao usar tipos b\u00e1sicos (Int, String, Float, etc.) n\u00e3o h\u00e1 necessidade de escrever c\u00f3digo adicional para converter estruturas.<\/ p><\/p>\n<p>A dissocia\u00e7\u00e3o de um codificador e decodificador espec\u00edfico ocorre usando abstra\u00e7\u00e3o no protocolo DataTransformer:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\tfunc encode&lt;T: Encodable&gt;(_ object: T) async throws -&gt; Data\n\tfunc decode&lt;T: Decodable&gt;(data: Data) async throws -&gt; T\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Usando a implementa\u00e7\u00e3o de um transformador de dados, implementamos uma abstra\u00e7\u00e3o das interfaces do codificador e do decodificador, implementando acoplamento fraco para garantir o trabalho com diferentes tipos de formatos de dados.<\/p>\n<p>A seguir est\u00e1 o c\u00f3digo para um DataTransformer espec\u00edfico, ou seja, para JSON:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\tfunc encode&lt;T&gt;(_ object: T) async throws -&gt; Data where T : Encodable {\n    \t\tlet data = try JSONEncoder().encode(object)\n    \t\treturn data\n\t}\n    \n\tfunc decode&lt;T&gt;(data: Data) async throws -&gt; T where T : Decodable {\n    \t\tlet item: T = try JSONDecoder().decode(T.self, from: data)\n    \t\treturn item\n\t}\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Foi poss\u00edvel?<\/p>\n<p>O que mudou? Agora basta inicializar um reposit\u00f3rio espec\u00edfico para funcionar com qualquer estrutura que implemente o protocolo Codable, eliminando assim a necessidade de duplica\u00e7\u00e3o de c\u00f3digo e implementa\u00e7\u00e3o de acoplamento fraco da aplica\u00e7\u00e3o.<\/p>\n<p>Um exemplo de cliente CRUD com um reposit\u00f3rio espec\u00edfico, UserDefaults \u00e9 o banco de dados, formato de dados JSON, estrutura do cliente, tamb\u00e9m um exemplo de escrita e leitura de um array:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nprint(\"One item access example\")\n\ndo {\n\tlet clientRecordIdentifier = \"client\"\n\tlet clientOne = Client(name: \"Chill Client\")\n\tlet repository = UserDefaultsRepository(\n    \ttableName: \"Clients Database\",\n    \tdataTransformer: JSONDataTransformer()\n\t)\n\ttry await repository.create(id: clientRecordIdentifier, item: clientOne)\n\tvar clientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Client Name: \\(clientRecord.name)\")\n\tclientRecord.name = \"Busy Client\"\n\ttry await repository.update(id: clientRecordIdentifier, item: clientRecord)\n\tlet updatedClient: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Updated Client Name: \\(updatedClient.name)\")\n\ttry await repository.delete(id: clientRecordIdentifier)\n\tlet removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(removedClientRecord)\n}\ncatch {\n\tprint(error.localizedDescription)\n}\n\nprint(\"Array access example\")\n\nlet clientArrayRecordIdentifier = \"clientArray\"\nlet clientOne = Client(name: \"Chill Client\")\nlet repository = UserDefaultsRepository(\n\ttableName: \"Clients Database\",\n\tdataTransformer: JSONDataTransformer()\n)\nlet array = [clientOne]\ntry await repository.create(id: clientArrayRecordIdentifier, item: array)\nlet savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)\nprint(savedArray.first!)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Durante a primeira verifica\u00e7\u00e3o CRUD, foi implementado o tratamento de exce\u00e7\u00f5es, em que a leitura do item remoto n\u00e3o estar\u00e1 mais dispon\u00edvel.<\/p>\n<p>Trocar bancos de dados<\/p>\n<p>Agora mostrarei como transferir seu c\u00f3digo atual para outro banco de dados. Por exemplo, pegarei o c\u00f3digo do reposit\u00f3rio SQLite que o ChatGPT gerou:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nclass SQLiteRepository: CRUDRepository {\n    private typealias RecordIdentifier = String\n    \n    let tableName: String\n    let dataTransformer: DataTransformer\n    private var db: OpaquePointer?\n\n    init(\n   \t tableName: String,\n   \t dataTransformer: DataTransformer = JSONDataTransformer()\n    ) {\n   \t self.tableName = tableName\n   \t self.dataTransformer = dataTransformer\n   \t self.db = openDatabase()\n   \t createTableIfNeeded()\n    }\n    \n    private func openDatabase() -&gt; OpaquePointer? {\n   \t var db: OpaquePointer? = nil\n   \t let fileURL = try! FileManager.default\n   \t\t .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)\n   \t\t .appendingPathComponent(\"\\(tableName).sqlite\")\n   \t if sqlite3_open(fileURL.path, &amp;db) != SQLITE_OK {\n   \t\t print(\"error opening database\")\n   \t\t return nil\n   \t }\n   \t return db\n    }\n    \n    private func createTableIfNeeded() {\n   \t let createTableString = \"\"\"\n   \t CREATE TABLE IF NOT EXISTS \\(tableName) (\n   \t id TEXT PRIMARY KEY NOT NULL,\n   \t data BLOB NOT NULL\n   \t );\n   \t \"\"\"\n   \t var createTableStatement: OpaquePointer? = nil\n   \t if sqlite3_prepare_v2(db, createTableString, -1, &amp;createTableStatement, nil) == SQLITE_OK {\n   \t\t if sqlite3_step(createTableStatement) == SQLITE_DONE {\n       \t\t print(\"\\(tableName) table created.\")\n   \t\t } else {\n       \t\t print(\"\\(tableName) table could not be created.\")\n   \t\t }\n   \t } else {\n   \t\t print(\"CREATE TABLE statement could not be prepared.\")\n   \t }\n   \t sqlite3_finalize(createTableStatement)\n    }\n    \n    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -&gt; Bool {\n   \t let queryStatementString = \"SELECT data FROM \\(tableName) WHERE id = ?;\"\n   \t var queryStatement: OpaquePointer? = nil\n   \t if sqlite3_prepare_v2(db, queryStatementString, -1, &amp;queryStatement, nil) == SQLITE_OK {\n   \t\t sqlite3_bind_text(queryStatement, 1, id, -1, nil)\n   \t\t if sqlite3_step(queryStatement) == SQLITE_ROW {\n       \t\t sqlite3_finalize(queryStatement)\n       \t\t return true\n   \t\t } else {\n       \t\t sqlite3_finalize(queryStatement)\n       \t\t return false\n   \t\t }\n   \t } else {\n   \t\t print(\"SELECT statement could not be prepared.\")\n   \t\t throw CRUDRepositoryError.databaseError\n   \t }\n    }\n    \n    func create&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n   \t let insertStatementString = \"INSERT INTO \\(tableName) (id, data) VALUES (?, ?);\"\n   \t var insertStatement: OpaquePointer? = nil\n   \t if sqlite3_prepare_v2(db, insertStatementString, -1, &amp;insertStatement, nil) == SQLITE_OK {\n   \t\t let data = try await dataTransformer.encode(item)\n   \t\t sqlite3_bind_text(insertStatement, 1, id, -1, nil)\n   \t\t sqlite3_bind_blob(insertStatement, 2, (data as NSData).bytes, Int32(data.count), nil)\n   \t\t if sqlite3_step(insertStatement) == SQLITE_DONE {\n       \t\t print(\"Successfully inserted row.\")\n   \t\t } else {\n       \t\t print(\"Could not insert row.\")\n       \t\t throw CRUDRepositoryError.databaseError\n   \t\t }\n   \t } else {\n   \t\t print(\"INSERT statement could not be prepared.\")\n   \t\t throw CRUDRepositoryError.databaseError\n   \t }\n   \t sqlite3_finalize(insertStatement)\n    }\n    \n    func read&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier) async throws -&gt; T {\n   \t let queryStatementString = \"SELECT data FROM \\(tableName) WHERE id = ?;\"\n   \t var queryStatement: OpaquePointer? = nil\n   \t var item: T?\n   \t if sqlite3_prepare_v2(db, queryStatementString, -1, &amp;queryStatement, nil) == SQLITE_OK {\n   \t\t sqlite3_bind_text(queryStatement, 1, id, -1, nil)\n   \t\t if sqlite3_step(queryStatement) == SQLITE_ROW {\n       \t\t let queryResultCol1 = sqlite3_column_blob(queryStatement, 0)\n       \t\t let queryResultCol1Length = sqlite3_column_bytes(queryStatement, 0)\n       \t\t let data = Data(bytes: queryResultCol1, count: Int(queryResultCol1Length))\n       \t\t item = try await dataTransformer.decode(data: data)\n   \t\t } else {\n       \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t\t }\n   \t } else {\n   \t\t print(\"SELECT statement could not be prepared\")\n   \t\t throw CRUDRepositoryError.databaseError\n   \t }\n   \t sqlite3_finalize(queryStatement)\n   \t return item!\n    }\n    \n    func update&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n   \t guard try await isExists(id: id) else {\n   \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t }\n   \t let updateStatementString = \"UPDATE \\(tableName) SET data = ? WHERE id = ?;\"\n   \t var updateStatement: OpaquePointer? = nil\n   \t if sqlite3_prepare_v2(db, updateStatementString, -1, &amp;updateStatement, nil) == SQLITE_OK {\n   \t\t let data = try await dataTransformer.encode(item)\n   \t\t sqlite3_bind_blob(updateStatement, 1, (data as NSData).bytes, Int32(data.count), nil)\n   \t\t sqlite3_bind_text(updateStatement, 2, id, -1, nil)\n   \t\t if sqlite3_step(updateStatement) == SQLITE_DONE {\n       \t\t print(\"Successfully updated row.\")\n   \t\t } else {\n       \t\t print(\"Could not update row.\")\n       \t\t throw CRUDRepositoryError.databaseError\n   \t\t }\n   \t } else {\n   \t\t print(\"UPDATE statement could not be prepared.\")\n   \t\t throw CRUDRepositoryError.databaseError\n   \t }\n   \t sqlite3_finalize(updateStatement)\n    }\n    \n    func delete(id: CRUDRepository.ItemIdentifier) async throws {\n   \t guard try await isExists(id: id) else {\n   \t\t throw CRUDRepositoryError.recordNotFound(id: id)\n   \t }\n   \t let deleteStatementString = \"DELETE FROM \\(tableName) WHERE id = ?;\"\n   \t var deleteStatement: OpaquePointer? = nil\n   \t if sqlite3_prepare_v2(db, deleteStatementString, -1, &amp;deleteStatement, nil) == SQLITE_OK {\n   \t\t sqlite3_bind_text(deleteStatement, 1, id, -1, nil)\n   \t\t if sqlite3_step(deleteStatement) == SQLITE_DONE {\n       \t\t print(\"Successfully deleted row.\")\n   \t\t } else {\n       \t\t print(\"Could not delete row.\")\n       \t\t throw CRUDRepositoryError.databaseError\n   \t\t }\n   \t } else {\n   \t\t print(\"DELETE statement could not be prepared.\")\n   \t\t throw CRUDRepositoryError.databaseError\n   \t }\n   \t sqlite3_finalize(deleteStatement)\n    }\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Ou o c\u00f3digo CRUD do reposit\u00f3rio do sistema de arquivos, que tamb\u00e9m foi gerado pelo ChatGPT:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nclass FileSystemRepository: CRUDRepository {\n\tprivate typealias RecordIdentifier = String\n    \n\tlet directoryName: String\n\tlet dataTransformer: DataTransformer\n\tprivate let fileManager = FileManager.default\n\tprivate var directoryURL: URL\n    \n\tinit(\n    \tdirectoryName: String = \"Database\",\n    \tdataTransformer: DataTransformer = JSONDataTransformer()\n\t) {\n    \tself.directoryName = directoryName\n    \tself.dataTransformer = dataTransformer\n   \t \n    \tlet paths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)\n    \tdirectoryURL = paths.first!.appendingPathComponent(directoryName)\n   \t \n    \tif !fileManager.fileExists(atPath: directoryURL.path) {\n        \ttry? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)\n    \t}\n\t}\n    \n\tprivate func fileURL(id: CRUDRepository.ItemIdentifier) -&gt; URL {\n    \treturn directoryURL.appendingPathComponent(\"item_\\(id).json\")\n\t}\n    \n\tprivate func isExists(id: CRUDRepository.ItemIdentifier) async throws -&gt; Bool {\n    \treturn fileManager.fileExists(atPath: fileURL(id: id).path)\n\t}\n    \n\tfunc create&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n    \tlet data = try await dataTransformer.encode(item)\n    \tlet url = fileURL(id: id)\n    \ttry data.write(to: url)\n\t}\n    \n\tfunc read&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier) async throws -&gt; T {\n    \tlet url = fileURL(id: id)\n    \tguard let data = fileManager.contents(atPath: url.path) else {\n        \tthrow CRUDRepositoryError.recordNotFound(id: id)\n    \t}\n    \tlet item: T = try await dataTransformer.decode(data: data)\n    \treturn item\n\t}\n    \n\tfunc update&lt;T: CRUDRepository.Item&gt;(id: CRUDRepository.ItemIdentifier, item: T) async throws {\n    \tguard try await isExists(id: id) else {\n        \tthrow CRUDRepositoryError.recordNotFound(id: id)\n    \t}\n    \tlet data = try await dataTransformer.encode(item)\n    \tlet url = fileURL(id: id)\n    \ttry data.write(to: url)\n\t}\n    \n\tfunc delete(id: CRUDRepository.ItemIdentifier) async throws {\n    \tguard try await isExists(id: id) else {\n        \tthrow CRUDRepositoryError.recordNotFound(id: id)\n    \t}\n    \tlet url = fileURL(id: id)\n    \ttry fileManager.removeItem(at: url)\n\t}\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Substitua o reposit\u00f3rio no c\u00f3digo do cliente:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nprint(\"One item access example\")\n\ndo {\n\tlet clientRecordIdentifier = \"client\"\n\tlet clientOne = Client(name: \"Chill Client\")\n\tlet repository = FileSystemRepository(\n    \tdirectoryName: \"Clients Database\",\n    \tdataTransformer: JSONDataTransformer()\n\t)\n\ttry await repository.create(id: clientRecordIdentifier, item: clientOne)\n\tvar clientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Client Name: \\(clientRecord.name)\")\n\tclientRecord.name = \"Busy Client\"\n\ttry await repository.update(id: clientRecordIdentifier, item: clientRecord)\n\tlet updatedClient: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Updated Client Name: \\(updatedClient.name)\")\n\ttry await repository.delete(id: clientRecordIdentifier)\n\tlet removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(removedClientRecord)\n}\ncatch {\n\tprint(error.localizedDescription)\n}\n\nprint(\"Array access example\")\n\nlet clientArrayRecordIdentifier = \"clientArray\"\nlet clientOne = Client(name: \"Chill Client\")\nlet repository = FileSystemRepository(\n\tdirectoryName: \"Clients Database\",\n\tdataTransformer: JSONDataTransformer()\n)\nlet array = [clientOne]\ntry await repository.create(id: clientArrayRecordIdentifier, item: array)\nlet savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)\nprint(savedArray.first!)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>A inicializa\u00e7\u00e3o de UserDefaultsRepository foi substitu\u00edda por FileSystemRepository, com os argumentos apropriados.<br \/>Ap\u00f3s executar a segunda vers\u00e3o do c\u00f3digo do cliente, voc\u00ea encontrar\u00e1 um diret\u00f3rio \u201cBanco de Dados de Clientes\u201d na pasta de documentos, que conter\u00e1 um arquivo de um array serializado em JSON com uma estrutura de Cliente.<\/p>\n<p>Alterar formato de armazenamento de dados<\/p>\n<p>Agora vamos pedir ao ChatGPT para gerar um codificador e decodificador para XML:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\tlet formatExtension = \"xml\"\n    \n\tfunc encode&lt;T: Encodable&gt;(_ item: T) async throws -&gt; Data {\n    \tlet encoder = PropertyListEncoder()\n    \tencoder.outputFormat = .xml\n    \treturn try encoder.encode(item)\n\t}\n    \n\tfunc decode&lt;T: Decodable&gt;(data: Data) async throws -&gt; T {\n    \tlet decoder = PropertyListDecoder()\n    \treturn try decoder.decode(T.self, from: data)\n\t}\n}\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Gra\u00e7as aos tipos integrados no Swift, a tarefa de uma rede neural torna-se elementar.<\/p>\n<p>Substitua JSON por XML no c\u00f3digo do cliente:<\/p>\n<div class=\"hcb_wrap\">\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\nprint(\"One item access example\")\n\ndo {\n\tlet clientRecordIdentifier = \"client\"\n\tlet clientOne = Client(name: \"Chill Client\")\n\tlet repository = FileSystemRepository(\n    \tdirectoryName: \"Clients Database\",\n    \tdataTransformer: XMLDataTransformer()\n\t)\n\ttry await repository.create(id: clientRecordIdentifier, item: clientOne)\n\tvar clientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Client Name: \\(clientRecord.name)\")\n\tclientRecord.name = \"Busy Client\"\n\ttry await repository.update(id: clientRecordIdentifier, item: clientRecord)\n\tlet updatedClient: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(\"Updated Client Name: \\(updatedClient.name)\")\n\ttry await repository.delete(id: clientRecordIdentifier)\n\tlet removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)\n\tprint(removedClientRecord)\n}\ncatch {\n\tprint(error.localizedDescription)\n}\n\nprint(\"Array access example\")\n\nlet clientArrayRecordIdentifier = \"clientArray\"\nlet clientOne = Client(name: \"Chill Client\")\nlet repository = FileSystemRepository(\n\tdirectoryName: \"Clients Database\",\n\tdataTransformer: XMLDataTransformer()\n)\nlet array = [clientOne]\ntry await repository.create(id: clientArrayRecordIdentifier, item: array)\nlet savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)\nprint(savedArray.first!)\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>O c\u00f3digo do cliente mudou para apenas uma express\u00e3o JSONDataTransformer -&gt; XMLDataTransformer<\/p>\n<p>Total<\/p>\n<p>Reposit\u00f3rios CRUD s\u00e3o um dos padr\u00f5es de design que podem ser usados \u200b\u200bpara implementar acoplamento fraco de componentes de arquitetura de aplicativos. Mais uma das solu\u00e7\u00f5es &#8211; usando ORM (Mapeamento Objeto-Relacional), resumindo, ORM usa uma abordagem em que as estruturas s\u00e3o completamente mapeadas para o banco de dados, e ent\u00e3o as altera\u00e7\u00f5es com modelos devem ser exibidas (mapeadas(!)) no banco de dados.<br \/>Mas essa \u00e9 uma hist\u00f3ria completamente diferente.<\/p>\n<p>Uma implementa\u00e7\u00e3o completa dos reposit\u00f3rios CRUD para Swift est\u00e1 dispon\u00edvel em:<br \/><a href=\"https:\/\/gitlab.com\/demensdeum\/crud-example\" rel=\"noopener\" target=\"_blank\">https:\/\/gitlab.com\/demensdeum\/crud-example<\/a><\/p>\n<p>A prop\u00f3sito, o Swift tem suporte fora do macOS h\u00e1 muito tempo; o c\u00f3digo do artigo foi totalmente escrito e testado no Arch Linux.<\/p>\n<p>Fontes<\/p>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/combine\/topleveldecoder\" rel=\"noopener\" target=\"_blank\">https:\/\/developer.apple.com\/documentation\/combine\/topleveldecoder <\/a><br \/><a href=\"https:\/\/developer.apple.com\/documentation\/combine\/toplevelencoder\" rel=\"noopener\" target=\"_blank\">https:\/\/developer.apple.com\/documentation\/combine\/toplevelencoder<\/a><br \/>\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Create,_read,_update_and_delete\" rel=\"noopener\" target=\"_blank\">https:\/\/en.wikipedia.org\/wiki\/Create,_read,_update_and_delete<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Nesta nota descreverei os princ\u00edpios b\u00e1sicos do conhecido padr\u00e3o cl\u00e1ssico CRUD, implementado na linguagem Swift. Swift \u00e9 uma linguagem aberta e multiplataforma dispon\u00edvel para Windows, Linux, macOS, iOS, Android. Existem muitas solu\u00e7\u00f5es para abstrair o armazenamento de dados e a l\u00f3gica do aplicativo. Uma dessas solu\u00e7\u00f5es \u00e9 a abordagem CRUD, que \u00e9 um acr\u00f4nimo para<a class=\"more-link\" href=\"https:\/\/demensdeum.com\/blog\/pt\/2024\/05\/28\/crud-repository\/\">Continue reading <span class=\"screen-reader-text\">&#8220;Reposit\u00f3rio CRUD&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[61],"tags":[224,223,225],"class_list":["post-3522","post","type-post","status-publish","format-standard","hentry","category-techie","tag-crud","tag-swift","tag-225","entry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"pt","enabled_languages":["en","ru","zh","de","fr","ja","pt","hi"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"ru":{"title":true,"content":true,"excerpt":false},"zh":{"title":true,"content":true,"excerpt":false},"de":{"title":true,"content":true,"excerpt":false},"fr":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false},"hi":{"title":false,"content":false,"excerpt":false}}},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3522","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/comments?post=3522"}],"version-history":[{"count":33,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3522\/revisions"}],"predecessor-version":[{"id":3867,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/3522\/revisions\/3867"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/media?parent=3522"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/categories?post=3522"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/pt\/wp-json\/wp\/v2\/tags?post=3522"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}