19 април, 2024

Георги Станев, Technical Lead и Full-Stack Developer в Prime Holding JSC

Разработването на комплексни мобилни приложения, които отговарят на високите очаквания на потребителите и същевременно консумират API със сървисно ориентирана архитектура, може да бъде предизвикателство в наши дни.

Нека разгледаме следните предизвикателства, с които бихме могли да се сблъскаме, докато работим върху реални приложения:

1. Междуфункционална комуникация;
2. Оптимизиране на API заявки, като спазваме добрите UX практики;
3. Изграждане на приложение, което може да обработва стотици хиляди, променящи се записи.

Междуфункционална комуникация

Представете си, че работите върху приложение, което има модули комуникиращи помежду си. Примерно списъци с обекти, които потребителят може да маркира като предпочитани или да променя по друг начин.
Нека да видим как би изглеждало това, ако приемем, че имаме два списъка. Първият съдържа всички обекти, а вторият тези, които сме маркирали като любими.

Потребителите виждат просто два списъка, но ние като софтуерни инженери виждаме много повече.

Нека приемем, че конкретния state management, който сме избрали за този проект, е BloC (Business Logic Component), с което трябва да имплементираме BloCs като FavoritesBloc, SearchBloc, ExtraDetailsBloc, PuppyManageBloc и др. Когато някой от обектите се актуализира и двата списъка (търсене и любими) трябва да бъдат актуализирани своевременно.

Бихме могли да подходим към комуникационното предизвикателството между модулите като създадем зависимости между блоковете. BloC A може да зависи от BloC B, BloC B от BloC C и т.н., но така може да се окажем с циклични зависимости, които се управляват трудно.

Нека видим как Coordinator шаблонът може да ни помогне.

Сега имаме централно място за междублокова комуникация. Така елиминираме преките зависимости между блоковете . Всеки BloC изпраща събития към Coordinator BloC-а и всеки BloC може да слуша за събития и да реагира своевременно.

Оптимизиране на API заявки, като спазваме добрите UX практики

Изграждането на API със сървисно ориентирана архитектура го прави скалируем, но понякога API endpoints стават много фрагментирани, с което трябва да се справим по някакъв начин в мобилното приложение.

Представете си, че с първата API заявка приложението може да извлече списък с обекти, но след това потребителите трябва да получат някои допълнителни детайли, които трябва да бъдат заредени по-късно, както е показано по-долу.

1. Приложението трябва да извлече допълнителните детайли чрез отделни API заявки, когато потребителите скролират бавно.
2. Приложението трябва да събере всички видими обекти и да получи тези допълнителни детайли само с една API заявка, когато потребителят скролира бързо и след това внезапно спре.

Сега изглежда подходящ момент да видите някакъв код, нали така?

puppies_extra_details.dart

puppies_exstra_details_bloc_extentions.dart

Използвайки този пакет, UI слоят може да изпрати видимите обекти към Business слоя. Тогава Business слоят може да ги буферира в 100 милисекунди интервали. Това означава, че бизнес слоят ще събира обекти и ще чака най-малко 100ms преди да извлече действителните допълнителни детайли. Така предоставяме най-добрия UX и в същото време постигаме оптимизация на API заявките.

Изграждане на приложение, което може да обработва стотици хиляди променящи се записи

Както знаем, Flutter е оптимизиран да работи много ефективно, но какво ще стане, ако трябва да доведем тази технология до предела на силите ѝ? А ако трябва да обработим стотици хиляди записи и същевременно искаме приложението да работи бързо и безпроблемно? Нека да видим как можем да постигнем това.

Опция 1

Можем да създаваме обекти от PuppyManage BloC, докато потребителят скролира през списъка. Това гарантира, че допълнителните детайли се извличат само, когато е необходимо и приложението все още отговаря на изискванията за оптимизирани API заявки, споменати по-горе.
Дотук добре, но така бихме имали един голям проблем, свързан с производителността на приложението. Когато потребителят скролира бързо, приложението трябва да създаде множество BloC обекти и същевременно потребителският интерфейс трябва да създаде множество BloC state subscriptions… съответно приложение ни ще стане много бавно.

Опция 2

Да създадем само един обект от всеки тип BloC, като например FavoritesBloc, SearchBloc, ExtraDetailsBloc, PuppyManageBloc и др. Нека да видим как би изглеждало това.

favorite_puppies_bloc.dart

puppy_list_bloc.dart

puppy_manage_bloc.dart

Можете да видите, че както търсачката, така и списъкът с любими имат собствен internal state, който се актуализира спрямо актуализациите на обектите, изпратени от ManagePuppy BloC чрез CoordinatorBloc. Това работи не само за маркиране на обект като любим, но и за извличане на допълнителни детайли. Чудесно, но задръжте за секунда … това означава ли, че имаме списък със стотици хиляди записи, които трябва да се актуализират спрямо всяка промяна? И приложението все още работи безпроблемно? Както споменах по-рано, Flutter е много бърз, така че да, приложението все още работи безотказно.

Архитектура: голямата картина

rx_bloc улеснява прилагането на BLoC state management, използвайки силата на реактивното програмиране.
Следвайки най-добрите практики за изграждане на стабилни мобилни приложения, архитектурата по-долу може да се използва заедно с BloC слоя.

Този пакет е създаден да работи заедно с rx_bloc_test, flutter_rx_bloc и rx_bloc_generator.

Заключение

Изграждането на приложения с реактивно програмиране в комбинация със солидна архитектура е наистина полезно. Приложенията стават по-стабилни, мащабируеми и по-лесни за поддържане.

Инвестирането в добра архитектура преди започване на нов проект винаги се отплаща в дългосрочен план.

Автор:  Георги Станев, Technical Lead и Full-Stack Developer в Prime Holding JSC

Тагове: , , , , , , , , , , , , , , , ,