icp2014: pimp dein apigility

Post on 28-Jun-2015

241 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Mit Apigility kann jeder Entwickler per Administrationsoberfläche einen REST oder RPC Web Service erstellen und betreiben. Apigility unterstützt zahlreiche Features wie HAL-JSON, Fehlerbehandlung, Versionierung, Authentifizierung und Dokumentation. Dies klappt prima für einfach strukturierte APIs. Doch können auch komplexere Strukturen umgesetzt werden oder stößt man hierbei schnell an die Grenzen? Dieser Vortrag zeigt Ihnen, wann Sie selber Hand an den generierten Code anlegen müssen und warum der Einsatz von Apigility auch bei komplexeren Services viel Sinn macht.

TRANSCRIPT

PIMP DEIN APIGILITYPIMP DEIN APIGILITYKomplexere Strukturen mit Apigility

1 / 46

Über michÜber mich

2 / 46www.RalfEggert.dewww.RalfEggert.de

[B00]

PrologProlog

3 / 46

[B01]

4 / 46

Komplexere Komplexere APIs mit APIs mit Apigility?Apigility?

[B02]

5 / 46

Geht auch!Geht auch!

DatenbankmodellDatenbankmodell

6 / 46

[B03]

ApigilityApigilityIn a NutshellIn a Nutshell

7 / 46

Restful Web ServicesRestful Web Services

CLIENT

Web Browser

PHP

Javascript

RUBY

PYTHON

REST SERVER

/api/user/123

/api/user

/api/user

/api/user/123

/api/user/123

USER DOMAIN

getUserEntity()

getUserCollection()

addUserEntity()

updateUserEntity()

deleteUserEntity()

GET Request

JSON Response

GET Request

JSON Response

POST Request

JSON Response

PUT Request

JSON Response

DELETE Request

JSON Response

Integer

UserEntity

void

UserCollection

Array

Boolean

Integer, Array

Boolean

Integer

Boolean

8 / 46

RPC Web ServicesRPC Web ServicesLocalCLIENT

javascript

RPCClient

JSON

Method:getUserParams:id

USER DOMAIN

getUserEntity()

GET Request

JSON Result

Integer

UserEntity

RPCServer

/json-rpc.php

Remote Call

JSON Result

javascriptJSON

Method:addUserParams:name

addUserEntity()POST Request

JSON Result

Array

Boolean

/json-rpc.phpRemote Call

JSON Result

javascriptXML

Method:getUserParams:id

getUserEntity()

GET Request

XML Result

Integer

UserEntity

/xml-rpc.php

Remote Call

XML Result

javascriptXML

Method:addUserParams:name

addUserEntity()

POST Request

XML Result

Array

Boolean

/xml-rpc.php

Remote Call

XML Result

9 / 46

VersionierungVersionierung

default Version per URLdefault Version per URL

Version 1 per URLVersion 1 per URL

Version 2 per URLVersion 2 per URL

default Version per Content Negotiationdefault Version per Content Negotiation

Version 1 per Content NegotiationVersion 1 per Content Negotiation

Version 2 per Content NegotiationVersion 2 per Content Negotiation

10 / 46

JSON / HAL / ProblemJSON / HAL / Problem

11 / 46

WEITERE FEATURESWEITERE FEATURES

Datenbank-basiertDatenbank-basiert Code-basiertCode-basiert authentifizierungauthentifizierung

API DokumentationAPI Dokumentation DatenvalidierungDatenvalidierung DeploymentDeployment

12 / 46

[B06][B05][B04]

[B09][B09][B08][B07]

[b10]

EinfacheEinfacheREST-ServicesREST-Services

13 / 46

DatenbankadapterDatenbankadapter

14 / 46

[b11]

REST SERVICE anlegenREST SERVICE anlegen

15 / 46

[b00]

AUTHENTIFIZIERUNGAUTHENTIFIZIERUNG

16 / 46

[b06]

Testen mit POSTMANTesten mit POSTMAN

1717 / / 4646

[b12][b12]

[B13]

FILTERN +FILTERN +SOrtierenSOrtieren

18 / 46

Collection query stringCollection query string

19 / 46

[b00]

FilterFilter SortSort

CodingCoding

20 / 46

[b00]

Users ResourceUsers Resource// module/User/src/User/V2/Rest/Users/UserResource.php

namespace User\V2\Rest\Users;

use Zend\Paginator\Adapter\DbTableGateway;use ZF\Apigility\DbConnectedResource;

class UsersResource extends DbConnectedResource{ public function fetchAll($data = array()) { $where = isset($data['filter']) ? $data['filter'] : array(); $order = isset($data['sort']) ? $data['sort'] : array();

$adapter = new DbTableGateway($this->table, $where, $order);

return new $this->collectionClass($adapter); }}

21 / 46

KonfigurationKonfiguration// module/User/config/module.config.php

return array( 'zf-apigility' => array( 'db-connected' => array( [...],

'User\\V2\\Rest\\Users\\UsersResource' => array( 'adapter_name' => 'MysqlAdapter', 'table_name' => 'users', 'hydrator_name' => 'Zend\\Stdlib\\Hydrator\\ArraySerializable', 'controller_service_name' => 'User\\V2\\Rest\\Users\\Controller', 'entity_identifier_name' => 'id', 'table_service' => 'User\\V2\\Rest\\Users\\UsersResource\\Table', 'resource_class' => 'User\\V2\\Rest\\Users\\UsersResource', ), ), ),);

22 / 46

Testen mit POSTMANTesten mit POSTMAN

2323 / / 4646

[b12][b12]

[B14]

KomplexereKomplexereREST-ServicesREST-Services

24 / 46

REST SERVICE anlegenREST SERVICE anlegen

25 / 46

[b00]

CodingCoding

26 / 46

[b00]

KonfigurationKonfiguration// module/User/config/module.config.php

return array( [...]

'service_manager' => array( 'factories' => array( 'User\\V3\\Rest\\UserProfiles\\UserProfilesResource' => 'User\\V3\\Rest\\UserProfiles\\UserProfilesResourceFactory', 'User\\V3\\Rest\\UserProfiles\\Table\\UserTable' => 'User\\V3\\Rest\\UserProfiles\\Table\\UserTableFactory', 'User\\V3\\Rest\\UserProfiles\\Table\\WebsiteTable' => 'User\\V3\\Rest\\UserProfiles\\Table\\WebsiteTableFactory', ), ),);

27 / 46

User Table IUser Table I// module/User/src/User/V3/Rest/UserProfiles/Table/UserTable.php

namespace User\V3\Rest\UserProfiles\Table;use Zend\Db\Adapter\AdapterInterface;use Zend\Db\ResultSet\ResultSetInterface;use Zend\Db\TableGateway\TableGateway;

class UserTable extends TableGateway{ public function __construct( AdapterInterface $adapter, ResultSetInterface $resultSetPrototype = null ) { $table = 'users';

parent::__construct($table, $adapter, null, $resultSetPrototype); }

public function fetchUserById($id) { $select = $this->getSql()->select(); $select->where->equalTo('id', $id);

return $this->selectWith($select)->current(); }

28 / 46

User Table IIUser Table II [...]

public function fetchContactsById($id) { $select = $this->getSql()->select(); $select->join('user_contacts', 'user_id_2 = id', array()); $select->where->equalTo('user_id_1', $id);

return $this->selectWith($select)->toArray(); }

public function fetchUsers($params) { $select = $this->getSql()->select();

return $this->selectWith($select)->toArray(); }}

29 / 46

User Table IIIUser Table III// module/User/src/User/V3/Rest/UserProfiles/Table/UserTableFactory.php

namespace User\V3\Rest\UserProfiles\Table;

use Zend\Db\ResultSet\ResultSet;use Zend\ServiceManager\FactoryInterface;use Zend\ServiceManager\ServiceLocatorInterface;

class UserTableFactory implements FactoryInterface{ public function createService(ServiceLocatorInterface $serviceLocator) { $dbAdapter = $serviceLocator->get('MysqlAdapter');

$resultSet = new ResultSet(ResultSet::TYPE_ARRAY);

$table = new UserTable($dbAdapter, $resultSet);

return $table; }}

30 / 46

User Profile entityUser Profile entity// module/User/src/User/V3/Rest/UserProfiles/UserProfilesEntity.php

namespace User\V3\Rest\UserProfiles;

class UserProfilesEntity{ protected $id; protected $firstname; protected $lastname; protected $email; protected $contacts; protected $websites;

public function setId($id) {} public function getId() {} public function setFirstname($firstname) {} public function getFirstame() {} public function setLastname($lastname) {} public function getLastame() {} public function setEmail($email) {} public function getEmail() {} public function setContacts(array $contacts) {} public function getContacts() {} public function setWebsites(array $websites) {} public function getWebsites() {}}

31 / 46

User Profile Resource IUser Profile Resource I// module/User/src/User/V3/Rest/UserProfiles/UserProfilesResource.php

namespace User\V3\Rest\UserProfiles;

use User\V3\Rest\UserProfile\Table\UserTable;use User\V3\Rest\UserProfile\Table\WebsiteTable;use ZF\ApiProblem\ApiProblem;use ZF\Rest\AbstractResourceListener;

class UserProfilesResource extends AbstractResourceListener{ protected $userTable; protected $websiteTable;

public function setUserTable($userTable) {} public function getUserTable() {} public function setWebsiteTable($websiteTable) {} public function getWebsiteTable() {}

public function create($data) { return new ApiProblem(405, 'The POST method has not been defined'); }

public function delete($id) {} public function deleteList($data) {}

32 / 46

User Profile Resource IIUser Profile Resource II public function fetch($id) { $user = $this->getUserTable()->fetchUserById($id); if (!$user) { return new ApiProblem(404, 'User profile for id ' . $id . ' not found'); } return $this->addContactsAndWebsites($user); }

public function fetchAll($params = array()) { $users = $this->getUserTable()->fetchUsers($params); if (!$users) { return new ApiProblem(404, 'No user profiles found'); } foreach ($users as $key => $user) { $users[$key] = $this->addContactsAndWebsites($user); } return $users; }

protected function addContactsAndWebsites(array $user) { $user['contacts'] = $this->getUserTable()->fetchContactsById($user['id']); $user['websites'] = $this->getWebsiteTable()->fetchWebsitesById($user['id']); return $user; }}

33 / 46

User Profile Resource IIIUser Profile Resource III// module/User/src/User/V3/Rest/UserProfiles/UserProfilesResourceFactory.php

namespace User\V3\Rest\UserProfiles;

class UserProfilesResourceFactory{ public function __invoke($services) { $userTable = $services->get( 'User\\V2\\Rest\\UserProfile\\Table\\UserTable' ); $websiteTable = $services->get( 'User\\V2\\Rest\\UserProfile\\Table\\WebsiteTable' );

$resource = new UserProfilesResource(); $resource->setUserTable($userTable); $resource->setWebsiteTable($websiteTable);

return $resource; }}

34 / 46

Testen mit POSTMANTesten mit POSTMAN

3535 / / 4646

[b12][b12]

[B13]

Datei UploadDatei Upload

36 / 46

Content-TypeContent-Type

37 / 46

[b00]

Multipart/form-dataMultipart/form-data

Image Field ergänzenImage Field ergänzen

38 / 46

[b00]

CodingCoding

39 / 46

[b00]

Users ResourceUsers Resource// module/User/src/User/V4/Rest/Users/UserResource.php

namespace User\V4\Rest\Users;use ZF\Apigility\DbConnectedResource;

class UsersResource extends DbConnectedResource{ protected function retrieveData($data) { if (null !== $filter) { $data = $this->getInputFilter()->getValues(); } else { $data = (array) $data; }

$image = $this->getInputFilter()->getValue('image'); $imagePath = '/img/uploads/' . md5(serialize($data)) . '.jpg'; $fullPath = APPLICATION_PATH . '/public' . $imagePath;

copy(APPLICATION_PATH . '/' . $image['tmp_name'], $fullPath); unlink(APPLICATION_PATH . '/' . $image['tmp_name']);

$data['image'] = $imagePath;

return $data; }}

40 / 46

Testen mit POSTMANTesten mit POSTMAN

4141 / / 4646

[b12][b12]

[B15]

Apigility ohne Apigility ohne ApigilityApigility

(Fast wie Kochen ohne Blumenkohl)(Fast wie Kochen ohne Blumenkohl)

42 / 46

Modularer AufbauModularer AufbauZf-apigilityZf-apigility Zf-apigility-adminZf-apigility-admin Zf-apigility-documentationZf-apigility-documentation

Zf-apigility-documentation-swaggerZf-apigility-documentation-swagger Zf-apigility-providerZf-apigility-provider Zf-apigility-WelcomeZf-apigility-Welcome

Zf-api-problemZf-api-problem Zf-configurationZf-configuration Zf-content-negotiationZf-content-negotiation

Zf-content-validationZf-content-validation Zf-deployZf-deploy Zf-development-modeZf-development-mode

Zf-halZf-hal Zf-mvc-authZf-mvc-auth Zf-oauth2Zf-oauth2

Zf-restZf-rest Zf-RPCZf-RPC Zf-versioningZf-versioning

43 / 46

[B00]

EPILOGEPILOG

44 / 46

PIMP DEIN APIGILITYPIMP DEIN APIGILITYKomplexere Strukturen mit Apigility

Repository: https://github.com/RalfEggert/ipc2014-apigility

45 / 46

BildnachweisBildnachweis[B00] Fotos von Ralf Eggert

[B01] Still here https://www.flickr.com/photos/thenovys/3784261365 von Abe Novy - CC-BY https://creativecommons.org/licenses/by/2.0/

[B02] Young student https://www.flickr.com/photos/audiolucistore/14159712431/ von www.audio-luci-store.it - CC-BY https://creativecommons.org/licenses/by/2.0/

[B03] Acorns https://www.flickr.com/photos/dno1967b/5431273344 von Daniel Oines - CC-BY https://creativecommons.org/licenses/by/2.0/

[B04] Fixing the database https://www.flickr.com/photos/dahlstroms/4140461901 von Håkan Dahlström - CC-BY https://creativecommons.org/licenses/by/2.0/

[B05] Monaco 14pt https://www.flickr.com/photos/polarity/3138680190 von Robert Agthe - CC-BY https://creativecommons.org/licenses/by/2.0/

[B06] RSA Securid Token - Credit Card Style https://www.flickr.com/photos/purpleslog/265657780 von Purple Slog - CC-BY https://creativecommons.org/licenses/by/2.0/

[B07] Shelf of Used Books https://www.flickr.com/photos/thedarkthing/5363586197 von William Ross - CC-BY https://creativecommons.org/licenses/by/2.0/

[B08] Ticket validator at Nice train station https://www.flickr.com/photos/traveleden/3797157077 von Simon - CC-BY https://creativecommons.org/licenses/by/2.0/

[B09] Test Lab - Supermicro Storage https://www.flickr.com/photos/jemimus/8533890844 von Robert - CC-BY https://creativecommons.org/licenses/by/2.0/

[B10] Building blocks https://www.flickr.com/photos/zscheyge/49012397 von Holger Zscheyge - CC-BY https://creativecommons.org/licenses/by/2.0/

[B11] Bluetooth adapter https://www.flickr.com/photos/razor512/8201846881 von Razor512 - CC-BY https://creativecommons.org/licenses/by/2.0/

[B12] Busy Postmen https://www.flickr.com/photos/wheatfields/4253690499 von Christian Guthier - CC-BY https://creativecommons.org/licenses/by/2.0/

[B13] Filters for black and white film https://www.flickr.com/photos/aslakr/96723190 von Aslak Raanes - CC-BY https://creativecommons.org/licenses/by/2.0/

[B14] Cable closet https://www.flickr.com/photos/adrian_s/8271860 von Adrian Sampson - CC-BY https://creativecommons.org/licenses/by/2.0/

[B15] Cauliflower https://www.flickr.com/photos/calliope/54833239 von Liz West - CC-BY https://creativecommons.org/licenses/by/2.0/

Alle weiteren Screenshots wurden von Ralf Eggert erstellt.

46 / 46

top related