Após a introdução do framework Crux, a terceira e última parte da atual série de artigos trata de conceitos avançados e integração prática em aplicações do mundo real. Os seguintes pontos sobre tipos técnicos, sua divisão em diversas aplicações e seu uso em tecnologias específicas de shell fornecem uma visão prática detalhada.
Leia mais depois do anúncio
Marcel Koch e sua equipe de sete pessoas assessoram pequenas e médias empresas e desenvolvem aplicações multiplataforma para desktop e mobile, bem como aplicações web entre indústrias – preferencialmente com TypeScript, Rust, Flutter ou Java, suportadas por CI/CD e IaC. Eles contam com soluções pragmáticas e personalizadas porque o software não é o fim de tudo. Além de seu sólido conhecimento técnico, ele treina comunicação não violenta, análise e agilidade de transações e promove uma visão crítica do hype da nuvem. Marcel é palestrante especialista, autor de artigos e livros e pode ser ouvido regularmente no podcast.
O tipo profissional
Em projetos de software de longo prazo, recomenda-se definir o tipo técnico independente do framework utilizado. Como parte do aplicativo de e-mail mostrado na seção anterior, por exemplo, estão disponíveis tipos EmailAddress e: Este tipo contém a validação e representação de endereços de e-mail e garante que apenas valores válidos sejam utilizados no sistema. Lógica para verificar – por exemplo, quanto à presença de símbolos @ – está vinculado diretamente no tipo e não faz parte da aplicação Crux.
Isto significa que a lógica técnica permanece claramente separada dos detalhes técnicos. Ele pode ser testado, reutilizado e desenvolvido independentemente do Crux, de outros frameworks ou de plataformas específicas. Se o ambiente técnico mudar, a lógica central permanecerá intacta e não precisará ser reescrita.
Um tipo tão profissional EmailAddress melhore a capacidade de manutenção e compreensão do código e proteja-o contra erros, evitando estados inválidos durante a criação.
Leia mais depois do anúncio
Listagem 1: Tipo técnico EmailAddress
#(derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize))
pub struct EmailAddress(String);
#(derive(thiserror::Error, Debug))
pub enum EmailError {
#(error("missing @"))
MissingAt,
}
impl EmailAddress {
/// Der Smart-Constructor garantiert eine gültige Adresse
pub fn parse(s: impl Into) -> Result {
let s = s.into();
if s.contains('@') { Ok(Self(s)) } else { Err(EmailError::MissingAt) }
}
pub fn as_str(&self) -> &str { &self.0 }
}
Um tipo tão profissional EmailAddress pode ser testado independentemente da estrutura.
Listagem 2: Teste para EmailAddress
#(cfg(test))
mod tests {
use super::*;
#(test)
fn valid_email_is_accepted() {
let email = EmailAddress::parse("marcel.koch@example.org");
assert!(email.is_ok());
assert_eq!(email.unwrap().as_str(), "marcel.koch@example.org");
}
#(test)
fn invalid_email_is_rejected() {
let email = EmailAddress::parse("marcel.kochexample.org");
assert!(email.is_err());
assert_eq!(email.unwrap_err(), EmailError::MissingAt);
}
}
O primeiro teste verifica se o endereço de e-mail correto foi recebido e armazenado corretamente. O segundo teste garante que o endereço de e-mail não é válido (sem @) é rejeitado e o erro correspondente (MissingAt) retornou.
Divisão em múltiplas aplicações ou caixas
Se a equipe de desenvolvimento de aplicativos estiver crescendo, pode fazer sentido dividir a equipe e o aplicativo em vários aplicativos e contêineres independentes. O gerenciamento de contatos pode estender seu aplicativo de e-mail existente e armazenar, atualizar ou excluir todos os dados de contato recebidos. O gerenciamento de contatos ganha seu próprio aplicativo Crux, gerenciado em uma caixa separada. Essas caixas separadas contêm recursos limitados e também são chamadas de caixas de recursos. Cada feature crate define seus próprios eventos, modelos, modelos de exibição e efeitos – portanto, os modelos fazem parte do recurso. O aplicativo principal combina caixas de recursos individuais e interações coordenadas.
Lista 3: contato (Feature-Crate)
// contacts/src/lib.rs
#(derive(Clone, Debug))
pub struct Contact {
pub name: String,
pub email: EmailAddress,
}
pub enum ContactsEvent {
AddContact(Contact),
RemoveContact(EmailAddress),
EditContact(EmailAddress, Contact),
}
#(derive(Default))
pub struct ContactsModel {
pub contacts: Vec,
}
pub struct ContactsViewModel {
pub contacts: Vec,
}
pub enum ContactsEffect {
ShowContactAdded(EmailAddress),
ShowContactRemoved(EmailAddress),
}
pub fn update(event: ContactsEvent, model: &mut ContactsModel) -> Vec {
match event {
ContactsEvent::AddContact(contact) => {
model.contacts.push(contact.clone());
vec!(ContactsEffect::ShowContactAdded(contact.email))
}
ContactsEvent::RemoveContact(email) => {
model.contacts.retain(|c| c.email != email);
vec!(ContactsEffect::ShowContactRemoved(email))
}
ContactsEvent::EditContact(email, new_contact) => {
if let Some(c) = model.contacts.iter_mut().find(|c| c.email == email) {
*c = new_contact.clone();
}
vec!()
}
}
}
pub fn view(model: &ContactsModel) -> ContactsViewModel {
ContactsViewModel {
contacts: model.contacts.clone(),
}
}
O recurso de e-mail assume a funcionalidade do aplicativo de e-mail anterior:
Lista 4: e-mail (Feature-Crate)
// email/src/lib.rs
pub enum EmailEvent {
SendEmail(EmailAddress),
EmailSent(bool),
}
#(derive(Default))
pub struct EmailModel {
pub last_sent: Option,
}
pub struct EmailViewModel {
pub last_sent: Option,
}
pub enum EmailEffect {
SendEmailRequest(String),
}
O aplicativo principal coleta modelos de recursos e coordenadas de comunicação:
Listagem 5: Haupt-App-Integration (App-Crate)
// app/src/lib.rs
use contacts::{ContactsEvent, ContactsModel, ContactsViewModel, ContactsEffect};
use email::{EmailEvent, EmailModel, EmailViewModel, EmailEffect};
pub enum AppEvent {
Email(EmailEvent),
Contacts(ContactsEvent),
}
pub struct AppModel {
pub email: EmailModel,
pub contacts: ContactsModel,
}
pub struct AppViewModel {
pub email: EmailViewModel,
pub contacts: ContactsViewModel,
}
pub enum AppEffect {
Email(EmailEffect),
Contacts(ContactsEffect),
}
pub fn update(event: AppEvent, model: &mut AppModel) -> Vec {
match event {
AppEvent::Email(email_event) => {
email::update(email_event, &mut model.email)
.into_iter().map(AppEffect::Email).collect()
}
AppEvent::Contacts(contacts_event) => {
contacts::update(contacts_event, &mut model.contacts)
.into_iter().map(AppEffect::Contacts).collect()
}
}
}
pub fn view(model: &AppModel) -> AppViewModel {
AppViewModel {
email: email::view(&model.email),
contacts: contacts::view(&model.contacts),
}
}
Esta divisão mantém aplicações modulares, testáveis e extensíveis. Cada recurso gerencia seu próprio estado e pode ser desenvolvido, testado e gerenciado de forma independente. O aplicativo principal cuida de orquestrar e combinar ViewModels individuais em uma interface consistente. Isso cria uma arquitetura escalável.



