React

Deklaratywne widoki na skalę Internetu.

A co gdyby użytkownik był funkcją…?

@radekmie

Czym jest React?

Biblioteką do tworzenia interfejsów.

Tak, tylko biblioteką.

Tradycyjne MV*

Model Kontroler Widok
Dane Logika interfejsu Szablony

Osobno?

React

Dane

Widoki

Komponenty

Akcje

Zmysły

<Application>
  <Navigation>
    <Logo />
  </Navigation>

  <Sidebar>
    <Profile>
      <Avatar of={42} />
      <Bio text={bio} />
    </Profile>

    <Friends of={42} />
    <Options of={42} />
  </Sidebar>

  <Timeline of={42} />
</Application>
<div class="application">
  <div class="navigation">
    <div class="logo">
      <img src="./logo.png" alt="logo" />
    </div>
  </div>

  <div class="sidebar">
    <div class="profile">
      <div class="avatar">
        <img src="./avatars/42.png" alt="avatar" />
      </div>
      <p class="bio">
        I'm a secret donut lover.
      </p>
    </div>

    <div class="friends">...</div>
    <div class="options">...</div>
  </div>

  <div class="timeline">...</div>
</div>

Deklaratywne widoki

const Timeline = props => {
  const events = getEvents(props.of).map(event =>
    <TimelineEvent key={event.id} event={event} />
  );

  return (
    <div className="timeline">
      {events}
    </div>
  );
}

Przykładowe komponenty

const Timeline = props => {
  const events = getEvents(props.of).map(event =>
    <TimelineEvent key={event.id} event={event} />
  );

  return (
    <div className="timeline">
      {events}
    </div>
  );
}

const TimelineEvent = props =>
  <div className="event">
    <h1>
      {props.event.title}
    </h1>
    <Avatar of={props.event.author} />
    <Schedule at={props.event.date} />
    <p>
      {props.event.description}
    </p>
  </div>
;

JSX?

const Timeline = props => {
  const events = getEvents(props.of).map(event =>
    React.createElement(TimelineEvent, {key: event.id, event: event})
  );

  return (
    React.createElement('div', {className: 'timeline'},
      events
    )
  );
}

const TimelineEvent = props =>
  React.createElement('div', {className: 'event'},
    React.createElement('h1', null,
      props.event.title
    ),
    React.createElement(Avatar, {of: props.event.author}),
    React.createElement(Schedule, {at: props.event.date}),
    React.createElement('p', null,
      props.event.description
    )
  )
;

Na prawdę deklaratywne?

To, co zwracają nasze komponenty, to nie fragmenty DOM, tylko opis interfejsu.

React zajmuje tylko się urzeczywistnieniem tego opisu.

Jednokierunkowy przepływ danych

Komponent rodzic przekazuje dziecku
jakieś informacje (ang. props).

Jak dziecko komunikuje się ze światem?

Odwrócony przepływ danych

class Navigation extends React.Component {
  onSearch = input => {
    alert(`Search: ${input}`);
  };

  render() {
    return (
      <SearchBar onSearch={this.onSearch} />
    );
  }
}
class SearchBar extends React.Component {
  onChange = event => {
    this.props.onSearch(event.target.value);
  };

  render() {
    return (
      <input className="search" onChange={this.onChange} />
    );
  }
}

Stan

class Navigation extends React.Component {
  state = {search: ''};

  onSearch = input => {
    this.setState({search: input});
  };

  render() {
    return (
      <SearchBar search={this.state.search} onSearch={this.onSearch} />
    );
  }
}
class SearchBar extends React.Component {
  onChange = event => {
    this.props.onSearch(event.target.value);
  };

  render() {
    return (
      <input className="search" onChange={this.onChange} />
    );
  }
}

Bez zmian!

Pytania?

GraphQL

Jedno API, by je wszystkie złączyć.

Język komunikacji między serwerem a klientem.

Maciek Stasiełuk

REST

REpresentational State Transfer

HTTP GET /book/1
{
  "id": 1,
  "title": "Hobbit",
  "authorId": 100
}
HTTP GET /author/100
{
  "id": 100,
  "firstName": "John Ronald Reuel",
  "lastName": "Tolkien"
}
HTTP GET /author/100/books
{
  "books": [
    {"id": 1, "title": "Hobbit"},
    {"id": 2, "title": "Drużyna Pierścienia"},
    {"id": 3, "title": "Dwie wieże"},
    {"id": 4, "title": "Powrót króla"}
  ]
}
HTTP GET /book/1
{
  "id": 1,
  "title": "Hobbit",
  "authorId": 100
}
HTTP GET /authorWithBooks/100
{
  "id": 100,
  "firstName": "John Ronald Reuel",
  "lastName": "Tolkien",
  "books": [
    {"id": 1, "title": "Hobbit"},
    {"id": 2, "title": "Drużyna pierścienia"},
    {"id": 3, "title": "Dwie wieże"},
    {"id": 4, "title": "Powrót króla"}
  ]
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien"
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 320
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 279,
  "publisher": "Longmans",
  "year": 1937
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "authorId": 100
}
HTTP GET /book/1
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 279,
  "publisher": "Longmans",
  "year": 1937
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 279,
  "publisher": "Longmans",
  "year": 1937,
  "cover": "hard"
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 279,
  "publisher": "Longmans",
  "year": 1937,
  "cover": "hard",
  "imageUrl": "..."
}
{
  "id": 1,
  "title": "Hobbit",
  "isbn": "9780582186552",
  "author": "John Ronald Reuel Tolkien",
  "pages": 279,
  "publisher": "Longmans",
  "year": 1937,
  "cover": "hard",
  "imageUrl": "...",
  "thumbnailUrl": "..."
}

Język programowania

Protokół

Framework

Język zapytań

GraphQL

book (id: 1) {
  id
  title
}
{
  "book": {
    "id": 1,
    "title": "Hobbit"
  }
}
book(id: 1) {
  id
  title
  author {
    firstName
    lastName
  }
  year
  publisher
  pages
}
{
  "book": {
    "id": 1,
    "title": "Hobbit",
    "author": {
      "firstName": "John Ronald Reuel",
      "lastName": "Tolkien"
    },
    "year": 1937,
    "publisher": "Longmans",
    "pages": 279
  }
}
author(id: 100) {
  firstName
  lastName
  books {
    title
  }
}
{
  "author": {
    "firstName": "John Ronald Reuel",
    "lastName": "Tolkien",
    "books": [
      {
        "title": "Hobbit"
      },
      {
        "title": "Drużyna Pierścienia"
      },
      {
        "title": "Dwie wieże"
      },
      {
        "title": "Powrót króla"
      }
    ]
  }
}
book(id: 2) {
  isbn
  title
  author {
    lastName
    books {
      title
      year
    }
  }
}
{
  "book": {
    "isbn": "9788324132614",
    "title": "Drużyna Pierścienia",
    "author": {
      "lastName": "Tolkien",
      "books": [
        {
          "title": "Hobbit",
          "year": 1937
        },
        {
          "title": "Drużyna Pierścienia",
          "year": 2009
        },
        {
          "title": "Dwie wieże",
          "year": 1981
        },
        {
          "title": "Powrót króla",
          "year": 2002
        }
      ]
    }
  }
}
mutation ($book: BookInput!) {
  addNewBook(book: $book) {
    id
    title
    pages
  }
}
{
  "addNewBook": {
    "id": 5,
    "title": "Silmarillion",
    "pages": 367
  }
}

Mutacje

const bookInput = {
  title: 'Silmarillion',
  publisher: 'Amber',
  year: 2014,
  pages: 367,
};

Zalety

  • Brak problemu pobierania nadmiarowych, zbędnych danych
     
  • Nie ma potrzeby wykonywania wielu zapytań żeby uzyskać wszystkie potrzebne dane
     
  • Szybki i niezależny rozwój aplikacji klienckich
     
  • Silnie typowana schema działa jak "kontrakt" między klientem a serwerem

Zalety cd.

  • Bardzo dokładna analityka użycia API
     
  • Możliwość łączenia istniejących API (w tym RESTowych) w formie API Gateway
     
  • Subskrypcje umożliwiające pobieranie danych w czasie rzeczywistym
     
  • Bogaty i stabilny ekosystem open-source
type Book {
    id: Int!
    title: String!
    isbn: String
    author: Author
    authorId: Int
    pages: Int
    publisher: String
    year: Int
    cover: CoverType
    imageUrl(size: ImageSize): String
}

type Author {
    id: Int!
    firstName: String!
    lastName: String!
    books: [Book]
}

type Query {
    author(id: Int!): Author
    book(id: Int!): Book
    books: [Book]
}

GraphQL SDL

Podstawowe typy

  • String
  • Boolean
  • Int
  • Float
  • ID

Enumeracje

enum CoverType {
  HARD
  SOFT
}

Własne typy

type Author {
    id: Int!
    firstName: String
    lastName: String!
}
type DateTime
enum ImageSize {
  FULL
  THUMB
}

Listy

  • [String]
  • [Int]
  • [Book]
  • ...

Fragmenty

Dyrektywy

Aliasy

Zmienne

Argumenty

book (id: 3) {
  imageUrl (size: "THUMB")
}

Demo

Integracja z resztą stacka

React

import {Query} from 'react-apollo';

const query = gql`{
    books {
        id
        title
        author {
            lastName
        }
    }
}`;

const BookListing = () => (
    <Query query={query}>
        {({data}) => (
            <ul>
                {data.books.map(({id, title, author}) => (
                    <li key={id}>{title} - {author.lastName}</li>
                ))}
            </ul>
        )}
    </Query>
);

MongoDB

const resolvers = {
    Query: {
        author: (root, args) => {
            return Authors.findOne({_id: args.id})
        },
        book: (root, args) => {
            return Books.findOne({_id: args.id})
        },
        books: () => {
            return Books.find()
        },
    }
};

Kto używa GraphQL produkcyjnie?

Pytania?

MongoDB

Old-school NoSQL.

Baza danych w języku Internetu.

@radekmie

NoSQL

Grafowe
Klucz-wartość
Kolumnowe
Dokumentowe
Neo4j
Redis
Neo4j
Neo4j
Redis
Cassandra
Neo4j
Redis
Cassandra
MongoDB

Dokumenty

_id

BSON

  • cały JSON
  • dane binarne
  • daty
  • wyrażenia regularne
  • wyspecjalizowane typy liczbowe

CRUD

db.createCollection('example')
db.createCollection('example')

db.getCollection('example').insertOne({
  key: 'value',
  and: {objects: ['are', 1337]}
})
db.createCollection('example')

db.getCollection('example').insertOne({
  key: 'value',
  and: {objects: ['are', 1337]}
})

db.getCollection('example').insertMany([
  {key: 17},
  {key: 20},
  {key: 42},
  {key: [1, 2, 3]}
])

CRUD

db.getCollection('example').findOne()

{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}

CRUD

db.getCollection('example').find()

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
    "key" : 17.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
    "key" : 20.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "key" : 42.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
    "key" : [ 1.0, 2.0, 3.0 ]
}]

CRUD

db.getCollection('example').find({key: 42})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "key" : 42.0
}]
db.getCollection('example').find({key: 42})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "key" : 42.0
}]

db.getCollection('example').find({key: {$gt: 17}})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
    "key" : 20.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "key" : 42.0
}]

CRUD

db.getCollection('example').find({
  key: {$in: ['value', 17]}
})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
    "key" : 17.0
}]

CRUD

db.getCollection('example').find({key: /va..e/})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}]
db.getCollection('example').find({key: /va..e/})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}]

db.getCollection('example').find({key: /.*/})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value",
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}]

CRUD

db.getCollection('example')
  .find({}, {key: 1})
  .sort({key: -1})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : "value"
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "key" : 42.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
    "key" : 20.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
    "key" : 17.0
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
    "key" : [ 
        1.0, 
        2.0, 
        3.0
    ]
}]

CRUD

db.getCollection('example')
  .find()
  .sort({key: -1})
  .skip(2)
  .limit(1)

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
    "key" : 20.0
}]

CRUD

$, $all, $and, $bitsAllClear, $bitsAllSet, $bitsAnyClear, $bitsAnySet, $comment, $elemMatch, $elemMatch, $eq, $exists, $expr, $geoIntersects, $geoWithin, $gt, $gte, $in, $jsonSchema, $lt, $lte, $meta, $mod, $ne, $near, $nearSphere, $nin, $nor, $not, $or, $regex, $size, $slice, $text, $type, $where

CRUD

db.getCollection('example')
  .updateOne({key: 'value'}, {$set: {key: 2}})
db.getCollection('example').findOne({key: 2})

{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : 2.0,
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}

CRUD

db.getCollection('example')
  .updateOne({key: 2}, {$inc: {key: 10}})
db.getCollection('example').findOne({key: 12})

{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : 12.0,
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}

CRUD

db.getCollection('example')
  .updateOne({key: 12}, {$addToSet: {'and.objects': 1}})
db.getCollection('example').findOne({key: 12})

{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : 12.0,
    "and" : {
        "objects" : [ 
            "are", 
            1337.0, 
            1.0
        ]
    }
}

CRUD

db.getCollection('example')
  .updateOne({key: 12}, {$pull: {'and.objects': 1}})
db.getCollection('example').findOne({key: 12})

{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "key" : 12.0,
    "and" : {
        "objects" : [ 
            "are", 
            1337.0
        ]
    }
}

CRUD

db.getCollection('example')
  .updateMany({}, {$set: {x: Math.PI}})
db.getCollection('example').find({}, {x: 1})

[{
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc7"),
    "x" : 3.14159265358979
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc8"),
    "x" : 3.14159265358979
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdc9"),
    "x" : 3.14159265358979
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdca"),
    "x" : 3.14159265358979
}, {
    "_id" : ObjectId("5adf8c4978f652fd5a50bdcb"),
    "x" : 3.14159265358979
}]

CRUD

$, $[<identifier>], $[], $addToSet, $bit, $currentDate, $each, $inc, $max, $min, $mul, $pop, $position, $pull, $pullAll, $push, $rename, $set, $setOnInsert, $slice, $sort, $unset

CRUD

db.getCollection('example').deleteOne({})
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').find()

[]
db.getCollection('example').deleteOne({})
db.getCollection('example').deleteMany({})
db.getCollection('example').find()

[]

db.getCollection('example').drop()

Agregacje

db.getCollection('likes').distinct('userId')

[
  "vq13u8ey9d",
  "tb8m0ovqrp",
  "zn3ye8enyz",
  "4rqmk6pg0h",
  "oslff18b7u",
  ...
]

Agregacje

db.getCollection('likes').aggregate([
  {$match: {date: {$gte: new Date(2016, 0, 1)}}},
  {$group: {_id: {$year: '$date'}, likes: {$sum: 1}}},
  {$sort: {likes: -1}}
])
db.getCollection('likes').aggregate([
  {$match: {date: {$gte: new Date(2016, 0, 1)}}},
  {$group: {_id: {$year: '$date'}, likes: {$sum: 1}}},
  {$sort: {likes: -1}}
])

[{
  _id: 2017.0,
  likes: 87291.0
}, {
  _id: 2016.0,
  likes: 53197.0
}, {
  _id: 2018.0,
  likes: 17234.0
}]

Agregacje

$addFields, $bucket, $bucketAuto, $collStats, $count, $currentOp, $facet, $geoNear, $graphLookup, $group, $indexStats, $limit, $listLocalSessions, $listSessions, $lookup, $match, $out, $project, $redact, $replaceRoot, $sample, $skip, $sort, $sortByCount, $unwind

Agregacje

$abs, $add, $addToSet, $allElementsTrue, $and, $anyElementTrue, $arrayElemAt, $arrayToObject, $avg, $ceil, $cmp, $concat, $concatArrays, $cond, $dateFromParts, $dateFromString, $dateToParts, $dateToString, $dayOfMonth, $dayOfWeek, $dayOfYear, $divide, $eq, $exp, $filter, $first, $floor, $gt, $gte, $hour, $ifNull, $in, $indexOfArray, $indexOfBytes, $indexOfCP, $isArray, $isoDayOfWeek, $isoWeek, $isoWeekYear, $last, $let, $literal, $ln, $log, $log10, $lt, $lte, $map, $max, $mergeObjects, $meta, $millisecond, $min, $minute, $mod, $month, $multiply, $ne, $not, $objectToArray, $or, $pow, $push, $range, $reduce, $reverseArray, $second, $setDifference, $setEquals, $setIntersection, $setIsSubset, $setUnion, $size, $slice, $split, $sqrt, $stdDevPop, $stdDevSamp, $strLenBytes, $strLenCP, $strcasecmp, $substr, $substrBytes, $substrCP, $subtract, $sum, $switch, $toLower, $toUpper, $trunc, $type, $week, $year, $zip

Pytania?