Interfejs do komunikacji ipc między qml-em a middleware'm przy pomocy Qt remote objects

0

Czołem Bracia i Siostry w kodzie

Nie będzie to tak do końca typowy post z zapytaniem o pomoc, jako że rozwiązanie jakie zastosowałem działa. Chciałbym jedynie się dowiedzieć, czy gdzieś można coś by usprawnić.

Wstęp do sytuacji przed zmianą wygląda tak:

  1. Są sobie 2 niezależne aplikacje - middleware które dostarcza różnakie dane, oraz GUI napisane w qmlu.
  2. Komunikują się ze sobą przy pomocy współdzielonych w .rep definicji dla Qt remote objects, używając url np. "tcp://127.0.0.1:19991". Bardzo możliwe, iż w przyszłości adres ten będzie w pełni sieciowy, a nie na tej samej maszynie.

Taka architektura działa OK, jednak sęk w tym, iż bezpośrednio w kodzie qmlowym są odwołania do owych zdalnych obiektów, więc jakiekolwiek przerobienie ich definicji wymusi zmiany w qmlu; już była zresztą sytuacja iż gościu z innego zespołu rzeźbił nam w plikach qmlowych bo się adres url do komunikacji zmienił. Poza tym część ludzi w moim zespole C++ nie ogarnia nawet trochę.
Zeźliłem się, i klnąc się na Peruna, Trygława, Swaroga i Welesa zapowiedziałem, iż tak zostać to nie może.

Postawiłem sobie za cele:

  1. Schować używanie zdalnych obiektów i niskopoziomowego C++ w qmlu.
  2. Sprawić, żeby komunikacja qmla z middleware'm była prosta, wygodna, i nie wymagająca znajomości co tam się w niższej warstwie dzieje.
  3. Uniezależnić się jak najbardziej od innych modułów i zespołów (definicje obiektów zdalnych będzie robił ktoś inny)
  4. Architektura ma generalnie spełniać KISS i YAGNI, oraz być niekłopotliwa w dodawaniu nowych zabawek/edycji istniejących.

Oto co stworzyłem.

Jest sobie klasa Communicator, będąca zbiorem interfejsów. Owe interfejsy zawierają w sobie repliki zdalnych obiektów, i robią mapowanie przychodzących z nich danych.

class Communicator : public QObject
{
   Q_OBJECT
   QML_ATTACHED(Communicator)
   QML_ANONYMOUS

   Q_PROPERTY( InterfaceSomeInterface *interfaceSome READ getInterfaceSome CONSTANT)
   Q_PROPERTY( InterfaceOtherInterface *interfaceOther READ getInterfaceOther CONSTANT)

public:
     static Communicator* qmlAttachedProperties(QObject *object)
     {
         static Communicator communicator;
         return &communicator;
     }

     InterfaceSomeInterface* getInterfaceSome() const;
     InterfaceOtherInterface * getInterfaceOther () const;

private:
    Communicator(QObject *parent = nullptr);//w konstruktorze są tworzone instancje interfejsów
};

Communicator rejestruje się w qmlu jako attached property jako Backend.

A tak wygląda interfejs:

class InterfaceSomeInterface : public QObject
{
  Q_OBJECT
  QML_ANONYMOUS

  //tu w zależności od tego, co chcę udostępnić w qmlu
  Q_PROPERTY(int neededData READ neededData NOTIFY neededDataChanged)

public:
  //konstruktor interfejsu, nic niezwykłego. W jego wnętrzu odbywa się np. połączenie sygnałów repliki z sygnałami interfejsu

 int neededData() const;//te gettery przeważnie robią replica.neededData();

private:
 SomeRemoteObjectReplica replica;

signals:
void neededDataChanged(int data);
};

Interfejs rejestruje się w qmlu jako uncreatable tudzież anonymous type

I jako to teraz w praktyce działa - w qmlu osoba, przyjmijmy iż jest to nieogar cplusplusowy, chce się dobrać do czegoś z backendu. Jedyne co musi zrobić, to użyć komendy:

Backend.interfaceSome.neededData

intellisense ładnie podpowiada nazwy zdefiniowane jako Q_PROPERTY. Mogę dzięki tym interfejsom robić mocki dla zdalnych obiektów których definicja jeszcze nie została uzgodniona, mogę w jedną klasę interfejsu dać kilka replik, albo na odwrót - stworzyć kilka interfejsów które będą działać na tej samej replice, jedynie koncentrować się na czymś innym.

I cóż o tym sądzicie Bracia/Siostry? Jakoś można to ulepszyć, tudzież zapominałem o czymś co mnie wkrótce ugryzie w zadziec?

0

Mhm, rozumiem, iż nic dodać, nic ująć.

0

Trochę zagmatwałeś pytanie. Rozumiem, że masz lokalny "serwer" i powiedzmy ~cienkiego klienta, które komunikują się po TCP/IP, ale nie podajesz jaki protokół jest używany i w którą stronę idzie komunikacja. Czy klient zawsze odpytuje serwer, czy konieczna jest również komunikacja w drugą stronę? Jakie są wymagania dotyczące opóźnień pomiędzy tymi komponentami?

Czy są jakieś przeciwskazania, żeby użyć np. ~rest, wymieniających jakiegoś json'a, czy inne protobuffy?

0

Bracie @piotrpo potrzebujesz zapoznać się w Qt Assistancie cóż to są Qt Remote Objects by w pełni zrozumieć sytuację.
Komunikacja pomiędzy aplikacjami owszem odbywa się po TCP/IP, jednakże owe RO są tak naprawdę nakładką na QLocalServer i QLocalSocket - to powoduje, iż takie szczegóły jak używany protokół nie są istotne. RO oferują mechanizm sygnał/slot(mniej więcej), ale działający jako IPC. Stąd też i resty, jsony i insze protobuffy są zbyteczne.

0

Ja tam się nie znam i chyba nie potrzebuję zapoznawać się z Qt Remote. Zdaje się, że część osób w twoim zespole, też nie ma na to ochoty:

Poza tym część ludzi w moim zespole C++ nie ogarnia nawet trochę.

Niemniej, dla mnie to po stronie tego co nazywasz middlewarem masz zewnętrzny interface aplikacji, czyli powinieneś mieć w tym miejscu zwykłą fasadę/adapter do logiki, schowanej wewnątrz. Jeżeli musisz/chcesz używać tych samych definicji obiektów po obu stronach, zamiast jakiegoś uniwersalnie zapisanego kontraktu, to nie ma cudów - zmiany po jednej stronie wymuszą zmiany po drugiej. Jedyne co możesz osiągnąć, to odizolować je od reszty każdego z kawałków.
Ale ja patrzę na to z mojego, głównie serwerowego punktu widzenia, gdzie "wincyj warsfuuuuffff" jest mocno w modzie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1