разработване на поддържаема RPC система

Работя върху уеб приложение, което ще използва широко AJAX техники за комуникация клиент/сървър... конкретно JSON-RPC. Zend Framework се използва от страна на сървъра и предлага хубав JSON-RPC сървър, който бих искал да използвам.

Моята цел е да създам поддръжка на система, която излага голямо подмножество от функционалност от страна на сървъра на страната на клиента (javascript), без ненужно дублиране на код. Виждал съм множество публикации в блогове и уроци за това как да използвам JSON-RPC сървъра на ZF (вижте тук и тук), но всички те изглеждаше насочен към излагане на малък, публично консумативен API. Дублирането на код е често срещано, например една публикация в блога разкрива следния метод:

public static function setTitle($bookId, $title) {
    $book = new Nickel_Model_Book($bookId);
    $book->setTitle($title);
    $book->update();
    return true;
}

Не ми харесва фактът, че има два setTitle метода. Ако сигнатурата на метода за един се промени, другият трябва да се поддържа синхронизиран... изглежда като кошмар за поддръжка, ако вашият API е обширен. Струва ми се, че трябва да има един Book клас с един setTitle метод.

Първоначалната ми мисъл е да добавя анотация на docblock @export към методи/класове, които искам да бъдат изложени. Когато реша да изложа метода setTitle, просто добавям анотацията, а не нов метод.

Един потенциален проблем, който виждам, включва постоянството на обекта. От страната на сървъра има смисъл setTitle да задава свойството заглавие на обекта... но не и да го запазва в базата данни, докато update() не бъде извикано. От страна на клиента извикването на setTitle трябва незабавно да засегне базата данни. Едно потенциално решение е да се модифицират всички инструменти за достъп, така че да приемат незадължителен втори параметър, което означава, че модификацията трябва незабавно да актуализира базата данни:

function setTitle($title, $persist = false) {
    $this->title = $title;

    if ($persist) $this->update();
}

Някакъв вид прокси клас може да гарантира, че флагът $persist е зададен за всички RPC извиквания от страна на клиента.

Друг проблем е сериализирането на PHP обекти. От страната на сървъра има смисъл да се направи OO стил $book->setTitle("foo") повикване, но book.setTitle(1234, "foo") от страна на клиента има смисъл (където 1234 е ID на книгата) поради липсата на състояние. Моето решение за това би било гореспоменатия прокси клас да бъде отговорен за превръщането по някакъв начин на book.setTitle(1234, "foo") в:

$book = new Book();
$book->load(1234);
return $book->setTitle($title);

Чувствам, че този проблем трябва да е бил разглеждан или обсъждан преди... но не намирам много ресурси онлайн. Това изглежда ли като разумно решение?


person Josh Johnson    schedule 22.11.2010    source източник
comment
Решихте ли този проблем или все още съществува? Бихте ли могли да публикувате вашето решение или да изтриете въпроса, или да ни кажете да започнем да мислим!   -  person markus    schedule 20.03.2011
comment
Понякога, когато модел за нещо, което смятате, че трябва да съществува, не съществува, това е, защото си представяте неправилно решение. Причината, поради която не искате просто да излагате големи количества методи като уеб услуга, е, че клиентът трябва да получи възможно най-подробни методи. Вашият дизайн на услугата трябва да произтича от нуждите на клиента. Клиентът трябва да има достъп до идеално прост интерфейс. Създавайте зърнести услуги за курсове и ги направете по-фини, както изискват композициите. Отделянето от публичен API и вашата библиотека си струва допълнителните усилия.   -  person JeremyWeir    schedule 07.06.2011
comment
Занимавам се с този точен проблем с nowjs.com и expressjs.com   -  person Heavy Gray    schedule 23.08.2011
comment
Ако разбирам добре: искате да се отървете от контролера и вместо това да извикате вашия модел директно с вашия изглед. не ?   -  person remi bourgarel    schedule 14.09.2011


Отговори (3)


Това, което търсите, се нарича Service Layer.

Техните обекти трябва да бъдат чисто контейнери за данни (освен ако не използвате Active Record), трябва да излагате само своя сервизен слой, а това на свой ред има достъп до техните обекти и съответните им методи.

Вашият клас Book е модел на домейн, сега трябва да създадете своя слой услуга

Вашият клас на обслужване ще бъде нещо подобно:

class BookService extends Service {

    //...

    public function changeBookTitle( $title, Book $book )
    {
        //verify if the $title is correct and valid
        $book->setTitle( $title );
        $this->methodToGetThePersistenceManager()->save( $book );

        //fire events, create a result object, etc...
    }
}
person jonathancardoso    schedule 09.09.2011

Няколко минути обмислях въпроса ви. Ако искате да опитате и това е в PHP, можете да направите някои магически методи на формата

набор от {Property} на {Object}.

Можете да направите това чрез магическия метод __call.

По този начин няма да се налага изрично да дефинирате методите, но свойствата пак ще бъдат зададени.

Магическият метод ще идентифицира кой тип обект трябва да създаде и кое свойство да зададе върху него и след това да продължи.

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

Имам чувството, че по някакъв начин не съм разбрал въпроса ви, но опитах.

person Jerry Saravia    schedule 09.06.2011

Добре,

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

Както и да е, вероятно ще трябва да направите повечето от това сами или просто да вмъкнете някакъв вид прехващач към всички извиквания на метод, ето бърз пример:

class Book {
  public $title = '';
  public $id = 0;
  public function setTitle($string) {
    $this->title = $string;
    echo "title called with $string\n";
  }
  public function twoArgs($arg1,$arg2) {
    echo "Multi-call: $arg1,$arg2\n";
  }
}
class Api {
  public function parse($json) {
    $jsonobj = json_decode($json,true);
    foreach ($jsonobj as $key=>$value) {
      $class_name = $key;
      $obj = new $class_name();
      foreach ($value as $vkey=>$vvalue) {
        if (method_exists($obj,$vkey)) {
          call_user_func_array(array($obj,$vkey),$vvalue);
        } else if (isset($obj->$vkey)) {
          echo "Setting $vkey\n";
          $obj->$vkey = $vvalue;
        }
      }
    }
  }
}

$json = json_encode(array('Book' => array('id' => 1234, 'setTitle' => array('The new title'))));
$api = new Api();
$api->parse($json);
$json = json_encode(array('Book' => array('id' => 1234, 'twoArgs' => array('arg1 :) ', 'arg2 :]'))));
$api->parse($json);

Очевидно бихте искали да добавите логика, за да се справите с постоянното маркиране и зареждането или да им позволите да преминат в конструктор: [args] и да се справите с това. и т.н. Когато става въпрос за излагане на функции, вие ги излагате в документация, всички те са достъпни, стига да са публични функции от този момент нататък и т.н.

person J. Martin    schedule 20.08.2011