Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
What to Upload to SlideShare
What to Upload to SlideShare
Loading in …3
×
1 of 26

A Cute app deserves a Clean architecture

2

Share

Download to read offline

An implementation of Clean Architecture principles for Qt and C++ apps, touching upon BDD and automated testing

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

A Cute app deserves a Clean architecture

  1. 1. A Cute app deserves a Clean architecture Marco Piccolino developer, consultant & trainer http://marcopiccolino.eu Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 1
  2. 2. Bricks, doors and windows Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 2
  3. 3. Bricks, doors and windows • Qt provides bricks, doors, windows etc. • Equivalent to a DIY megastore • What you do with those components is your problem • That's a good thing, as long as you have a blueprint for your application Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 3
  4. 4. Blueprints Fair use, https://en.wikipedia.org/w/index.php?curid=29612930 Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 4
  5. 5. Blueprints What are the rooms I need? How do they connect to eachother? Not much literature on application architecture approaches for Qt. Interesting examples: A Multilayered Architecture for Qt Quick - David Johnson http://bit.ly/QtQuickMultilayeredJohnson QML Application Architecture Guide with Flux – Ben Lau http://bit.ly/QmlFluxBenlau Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 5
  6. 6. Clean architecture Credits: http://timshapkin.livejournal.com/16693.html Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 6
  7. 7. Clean architecture (Robert C. Martin) Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 7
  8. 8. Clean architecture: advantages • Independent of Frameworks • Testable • Independent of UI • Independent of Database • Independent of external agencies Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 8
  9. 9. A cute implementation Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 9
  10. 10. Application layers Use cases Start from here to model application-specific business logic, i.e. interactions between entities Entities Business objects - for me they arise by reasoning about use cases Repositories They abstract & encapsulate data I/O Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 10
  11. 11. Use cases / 1 Can be modeled via Behaviour-Driven Development's features and scenarios. E.g.: Feature: Check available groceries I want to check groceries available in my fridge to know when to buy them before I run out of them Scenario: One or more grocery items available Given the list of available grocery items is empty And one or more grocery items are available When I check available groceries Then I am given the list of available grocery items And the grocery items are ordered by type, ascending Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 11
  12. 12. Use cases / 2 Preconditions (Given), actions (When) and expected results (Then) can modeled in QtTest or other testing frameworks. void Usecases_check_available_groceries::test_one_or_more_grocery_items_available() { // Given the list of available grocery items is empty auto groceryItems = new entities::GroceryItems(this); QCOMPARE(groceryItems->list().count(), 0); // And one or more grocery items are available auto groceryItemsDummy = new repositories::GroceryItemsDummy(groceryItems); QVERIFY(groceryItemsDummy->count() > 0); groceryItems->setRepository(groceryItemsDummy); Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 12
  13. 13. // When I check available groceries auto checkAvailableGroceries = new usecases::CheckAvailableGroceries(groceryItems, this); QSignalSpy checkAvailableGroceriesSuccess( checkAvailableGroceries, SIGNAL(success(QString))); checkAvailableGroceries->run(); QTRY_COMPARE_WITH_TIMEOUT(checkAvailableGroceriesSuccess.count(), 1, 1000); // Then I am given the list of available grocery items QCOMPARE(groceryItems->list().count(), groceryItemsDummy->count()); // And the grocery items are ordered by type, ascending QVERIFY(groceryItems->isSortedBy("type","ASC")); } Among other benefits, writing the tests first helps defining a clean API for usecases, entities and repositories. Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 13
  14. 14. Use cases / 3 • Entities can be passed to usecases as arguments: checkAvailableGroceries->run(groceryItems); • Alternatively, a global entity register can be created, and use cases get hold of entities from there • By creating mock repositories instead of real ones, use case tests run fast: auto groceryItemsDummy = new repositories::GroceryItemsDummy(groceryItems); Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 14
  15. 15. Use cases / 4 Running the use case: a sequence of interactions with and between entities. void CheckAvailableGroceries::run(entities::GroceryItems *groceryItems) { connect(groceryItems, &entities::GroceryItems::allRetrieved, this, &CheckAvailableGroceries::onGroceryItemsAllRetrieved, Qt::UniqueConnection); connect(groceryItems ,&entities::GroceryItems::allNotRetrieved, this, &CheckAvailableGroceries::onGroceryItemsAllNotRetrieved, Qt::UniqueConnection); groceryItems->retrieveAll(); } Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 15
  16. 16. Use cases / 5 Once all business logic for the use case is done, we emit a signal: void CheckAvailableGroceries::onGroceryItemsAllRetrieved() { emit success("CHECK_AVAILABLE_GROCERIES__SUCCESS"); } void CheckAvailableGroceries::onGroceryItemsAllNotRetrieved() { emit failure("CHECK_AVAILABLE_GROCERIES__FAILURE"); } Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 16
  17. 17. Entities / 1 • The API of entities arises from use cases or is already given: groceryItems->retrieveAll(); • Entities only implement business rules, and leave storage and data retrieval concerns to repositories: void GroceryItems::retrieveAll() { if (m_repository) { m_repository->retrieveAllRecords(); } } Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 17
  18. 18. Entities / 2 Once the data is retrieved, an entity performs its business logic: void GroceryItems::onAllRecordsRetrieved(QVariantList records) { // sort by type ascending ... // add records to list m_list->clear(); m_list->append(records); emit allRetrieved("ENTITIES_GROCERY_ITEMS_ALL_RETRIEVED"); } Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 18
  19. 19. Repositories / 1 Repositories encapsulate data storage, input and output. Thanks to this, dummy repositories (test doubles) can be employed while testing use cases and entities. Benefits: * Avoids coupling a specific technology (e.g. SQL, REST) to application logic * Much faster execution of use cases and entities test suites * Can switch backends as needed Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 19
  20. 20. Repositories / 2 class GroceryItemsDummy : public GroceryItemsRepo { Q_OBJECT public: explicit GroceryItemsDummy(QObject *parent = nullptr) :GroceryItemsRepo(parent){} int count() const { return 3; } void retrieveAllRecords() { QVariantList recordsArray; recordsArray.push_back(QVariantMap{{"type", "bananas"}}); recordsArray.push_back(QVariantMap{{"type", "apples"}}); recordsArray.push_back(QVariantMap{{"type", "cheese"}}); emit allRecordsRetrieved(recordsArray); } }; Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 20
  21. 21. Adding a CLI Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 21
  22. 22. Adding a CLI / 1 Once the use case is complete, we can add all kinds of user interfaces on top of it: int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); auto groceryItems = new entities::GroceryItems(&a); auto groceryItemsDummy = new repositories::GroceryItemsDummy(&a); auto checkAvailableGroceries = new usecases::CheckAvailableGroceries(&a); groceryItems->setRepository(groceryItemsDummy); Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 22
  23. 23. Adding a CLI / 2 Text output: QTextStream cout(stdout); QObject::connect(checkAvailableGroceries, &usecases::CheckAvailableGroceries::success, [&cout, groceryItems](QString message) { cout << message << endl; auto list = groceryItems->list(); QVariantList::const_iterator i; for (i = list.constBegin(); i != list.constEnd(); ++i) cout << i->toMap().value("type").toString() << endl; }); Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 23
  24. 24. Adding a CLI / 3 Text input: (cout << "Enter action: ").flush(); QTextStream cin(stdin); QString action(cin.readLine()); if (action == "check available groceries") { checkAvailableGroceries->run(groceryItems); a.exit(0); } else { cout << "Action not supported" << endl; a.exit(1); } } Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 24
  25. 25. Considerations • For thin clients, one or more layers could be written in QML/JS • This process seems tedious at first, but gains come later on (debugging, refactoring, new features, new user interfaces) • You might want to add more layers (e.g. Presenters). • I went through a few iterations and am still refining the reasoning. Would love feedback! Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 25
  26. 26. Thank you! The Clean Architecture (blog post) http://bit.ly/CleanArchBlog Clean Architecture (book) http://bit.ly/CleanArchBook Code examples https://github.com/marco-piccolino Marco Piccolino - A Cute app deserves a Clean architecture - http://marcopiccolino.eu - hello@marcopiccolino.eu 26

×