5個(gè)用/不用GraphQL的理由

我在如何使用Gatsby建立博客 / How to build a blog with Gatsby這篇文章中提過(guò)GraphQL在Gatsby中的應(yīng)用躲胳∨榷校總的來(lái)講,它是一個(gè)新潮的技術(shù),在適宜的使用場(chǎng)景威力無(wú)窮。這里我們來(lái)討論一下用/不用GraphQL的理由吧匠楚。

簡(jiǎn)單介紹GraphQL

GrahQL

GraphQL是Facebook2015年開(kāi)源的數(shù)據(jù)查詢規(guī)范。現(xiàn)今的絕大多數(shù)Web Service都是RESTful的厂财,也就是說(shuō)芋簿,client和server的主要溝通模式還是靠client根據(jù)自己的需要向server的若干個(gè)endpoint (url)發(fā)起請(qǐng)求。由于功能的日漸豐富璃饱,對(duì)Web Application的要求變得復(fù)雜与斤,REST的一些問(wèn)題逐漸暴露,人們開(kāi)始思考如何應(yīng)對(duì)這些問(wèn)題荚恶。GraphQL便是具有代表性的一種撩穿。GraphQL這個(gè)名字,Graph + Query Language裆甩,就表明了它的設(shè)計(jì)初衷是想要用類似圖的方式表示數(shù)據(jù):即不像在REST中冗锁,數(shù)據(jù)被各個(gè)API endpoint所分割齐唆,而是有關(guān)聯(lián)和層次結(jié)構(gòu)的被組織在一起嗤栓。

比方說(shuō),假設(shè)這么一個(gè)提供user信息的REST API: <server>/users/<id>箍邮,和提供用戶的關(guān)注者的API:<server>/users/<id>/followers茉帅,以及該用戶關(guān)注對(duì)象的API: <server>/users/<id>/followed-users。傳統(tǒng)的REST會(huì)需要3次API call才能請(qǐng)求出這三份信息(假設(shè)<server>/users/<id> 沒(méi)有包含followers and followed-users信息锭弊,which will be a definite redundancy if it does):
1 GET <server>/users/<id>

{
 "user": {
    "id" : "u3k2k3k178",
    "name" : "graph_ql_activist",
    "email" : "graph_ql@activist.com",
    "avatar" : "img-url"
  }
}

2 GET <server>/users/<id>/followed-users
3 GET <server>/users/<id>/followers

然而如果使用GraphQL堪澎,一次API請(qǐng)求即可獲取所有信息并且只選取需要的信息(比如關(guān)于用戶只需要name不要email, followers只要最前面的5個(gè)name,followed-users只要頭像等等):

query {
  user (id : "u3k2k3k178") {
    name
    followers (first: 5) {
      name
    }
    followed-users {
      avatar
    }
  }
}

我們會(huì)得到一個(gè)完全按照query定制的味滞,不多不少的返回結(jié)果(一般是一個(gè)json對(duì)象)樱蛤。

5個(gè)使用GraphQL的理由

使用GraphQL的理由钮呀, 必然是從討論RESTful Service的局限性和問(wèn)題開(kāi)始。

  1. 數(shù)據(jù)冗余和請(qǐng)求冗余 (overfetching & underfetching)
  2. 靈活而強(qiáng)類型的schema
  3. 接口校驗(yàn) (validation)
  4. 接口變動(dòng)昨凡,維護(hù)與文檔
  5. 開(kāi)發(fā)效率

1 數(shù)據(jù)冗余和請(qǐng)求冗余 (overfetching & underfetching)

根據(jù)users API的例子爽醋,我們可以想見(jiàn),GET用戶信息的REST call便脊,我們就算只是想要一個(gè)用戶的一兩條信息(比如name & avatar)蚂四,通過(guò)該API,我們也會(huì)得到他的整個(gè)信息哪痰。所謂的overfetching就是指的這種情況——請(qǐng)求包含當(dāng)前不需要的信息遂赠。這種浪費(fèi)會(huì)一定程度地整體影響performance,畢竟更多的信息會(huì)占用帶寬和占用資源來(lái)處理晌杰。

同樣從上面的例子我們可以看出來(lái)跷睦,在許多情況下,如果我們使用RESTful Application肋演,我們常常會(huì)需要為聯(lián)系緊密并總量不大的信息送讲,對(duì)server進(jìn)行多次請(qǐng)求,call復(fù)數(shù)個(gè)API惋啃。

舉一個(gè)例子哼鬓,獲取ID為"abc1"和"abc2"的兩個(gè)用戶的信息,我們可能都需要兩個(gè)API call边灭,一百個(gè)用戶就是一百個(gè)GET call异希,這是不是很莫名其妙呢?這種情況其實(shí)就是underfetching——API的response沒(méi)有合理的包含足夠信息绒瘦。

然而在GraphQL称簿,我們只需要非常簡(jiǎn)單地改變schema的處理方式,就可以用一個(gè)GET call解決:

query {
  user (ids : ["ab1", "abc2", ...])
}

我們新打開(kāi)一個(gè)網(wǎng)頁(yè)惰帽,如果是RESTful Application憨降,可能請(qǐng)求數(shù)據(jù)就會(huì)馬上有成百上千的HTTP Request,然而GraphQL的Application則可能只需要一兩個(gè)该酗,這相當(dāng)于把復(fù)雜性和heavy lifting交給了server端和cache層授药,而不是資源有限,并且speed-sensitive的client端呜魄。

2 靈活而強(qiáng)類型的schema

GraphQL是強(qiáng)類型的悔叽。也就是說(shuō),我們?cè)诙xschema時(shí)爵嗅,類似于使用SQL娇澎,是顯式地為每一個(gè)域定義類型的,比如說(shuō):

type User {
  id: ID!
  name: String!
  joinedAt: DateTime!
  profileViews: Int! @default(value: 0)
}

type Query {
  user(id: ID!): User
}

GraphQL的schema的寫作語(yǔ)言睹晒,其實(shí)還有一個(gè)專門的名稱——Schema Definition Language (SDL)趟庄。

這件事情的一大好處是括细,在編譯或者說(shuō)build這個(gè)Application時(shí),我們就可以檢查并應(yīng)對(duì)很多mis-typed的問(wèn)題戚啥,而不需要等到runtime勒极。同時(shí),這樣的寫作方式虑鼎,也為開(kāi)發(fā)者提供了巨大的便利辱匿。比如說(shuō)使用YAML來(lái)定義API時(shí),編寫本身就是十分麻煩的——可能沒(méi)有理想的auto-complete炫彩,語(yǔ)法或者語(yǔ)義有錯(cuò)無(wú)法及時(shí)發(fā)現(xiàn)匾七,文檔也需要自己小心翼翼地編寫。就算有許多工具(比如Swagger)幫助江兢,這仍然是一個(gè)很令人頭疼的問(wèn)題昨忆。

3 接口校驗(yàn) (validation)

顯而易見(jiàn),由于強(qiáng)類型的使用杉允,我們對(duì)收到的數(shù)據(jù)進(jìn)行檢驗(yàn)的操作變得更為容易和嚴(yán)格邑贴,自動(dòng)化的簡(jiǎn)便度和有效性也大大提高。對(duì)query本身的結(jié)構(gòu)的校驗(yàn)也相當(dāng)于是在schema完成后就自動(dòng)得到了叔磷,所以我們甚至不需要再引入任何別的工具或者依賴拢驾,就可以很方便地解決所有的validation。

4 接口變動(dòng)改基,維護(hù)與文檔

RESTful Application里面繁疤,一旦要改動(dòng)API,不管是增刪值域秕狰,改變值域范圍稠腊,還是增減API數(shù)量,改變API url鸣哀,都很容易變成傷筋動(dòng)骨的行為架忌。

如果說(shuō)改動(dòng)API url(比如/posts --> /articles),我們思考一下那些地方可能要改動(dòng)呢我衬?首先client端的代碼定然要改變r(jià)equest的API endpoint叹放;中間的caching service可能也需要改要訪問(wèn)的endpoint;如果有l(wèi)oad balancer, reverse proxy低飒,那也可能需要變動(dòng)许昨;server端自己當(dāng)然也是需要做相應(yīng)改變的懂盐,這根據(jù)application自己的編寫情況而定褥赊。

相比之下,GraphQL就輕松多了莉恼。GraphQL的Service拌喉,API endpoint很可能就只有一個(gè)速那,根本不太會(huì)有改動(dòng)URL path的情況。至始至終尿背,數(shù)據(jù)的請(qǐng)求方都只需要說(shuō)明自己需要什么內(nèi)容端仰,而不需要關(guān)心后端的任何表述和實(shí)現(xiàn)。數(shù)據(jù)提供方田藐,比如server荔烧,只要提供的數(shù)據(jù)是請(qǐng)求方的母集,不論它們各自怎么變汽久,都不需要因?yàn)閷?duì)方牽一發(fā)而動(dòng)全身鹤竭。

在現(xiàn)有工具下,REST API的文檔沒(méi)有到過(guò)分難以編寫和維護(hù)的程度景醇,不過(guò)跟可以完全auto-generate并且可讀性可以很好地保障的GraphQL比起來(lái)臀稚,還是略顯遜色——畢竟GraphQL甚至不需要我們費(fèi)力地引入多少其他的工具。

再一點(diǎn)三痰,我們都知道REST API有一個(gè)versioning: V1, V2, etc.這件事非常的雞肋而且非常麻煩吧寺,有時(shí)候還要考慮backward compatibility。GraphQL從本質(zhì)上不存在這一點(diǎn)散劫,大大減少了冗余稚机。增加數(shù)據(jù)的fields和types甚至不需要數(shù)據(jù)請(qǐng)求方做任何改動(dòng),只需要按需添加相應(yīng)queries即可获搏。

另外抒钱,有了GraphQL的queries,我們可以非常精準(zhǔn)地進(jìn)行數(shù)據(jù)分析(Analytics)颜凯。比如說(shuō)具體哪些queries下的fields / objects在哪些情況下是被請(qǐng)求的最多/最頻繁的——而不像RESTful Application中谋币,如果不進(jìn)行復(fù)雜的Analytics,我們只能知道每個(gè)API被請(qǐng)求的情況症概,而不是具體到它們內(nèi)含的數(shù)據(jù)蕾额。

5 開(kāi)發(fā)效率

相信上面說(shuō)的這些點(diǎn)已經(jīng)充分能夠說(shuō)明GraphQL對(duì)于開(kāi)發(fā)效率能夠得到怎樣的提升了。

再補(bǔ)充幾點(diǎn)彼城。

GraphQL有一個(gè)非常好的ecosystem诅蝶。由于它方便開(kāi)發(fā)者上手和使用-->大家爭(zhēng)相為它提供各種工具和支持-->GraphQL變得更好用-->社區(qū)文化和支持更盛-->... 如同其他好的開(kāi)源項(xiàng)目一樣,GraphQL有著一個(gè)非常好的循環(huán)正向反饋募壕。

對(duì)于一套R(shí)EST API调炬,哪怕只是其使用者(consumer),新接觸的開(kāi)發(fā)者需要一定時(shí)間去熟悉它的大致邏輯舱馅,要求乃至實(shí)現(xiàn)缰泡。然而GraphQL使用者甚至不需要去看類似API文檔的東西,因?yàn)槲覀兛梢灾苯油ㄟ^(guò)query查詢query里面所有層級(jí)的type的所有域和它們各自的type代嗤,這不得不說(shuō)很方便:

{
  __schema {
    types {
      name
    }
  }
}

==> 我們可以看到query所涉及的所有內(nèi)容的類型:

{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query"
        },
        {
          "name": "Episode"
        },
        {
          "name": "Character"
        },
        {
          "name": "ID"
        },
        {
          "name": "String"
        },
        {
          "name": "Int"
        },
        {
          "name": "FriendsConnection"
        },
        {
          "name": "FriendsEdge"
        },
        {
          "name": "PageInfo"
        }
        {
          "name": "__Schema"
        },
        {
          "name": "__Type"
        },
        {
          "name": "__TypeKind"
        },
        {
          "name": "__Field"
        },
        {
          "name": "__InputValue"
        },
        {
          "name": "__EnumValue"
        }
        }
      ]
    }
  }
}

對(duì)于GraphQL棘钞,我還有個(gè)非常個(gè)人的理由偏愛(ài)它:對(duì)于API的測(cè)試缠借,相比于比較傳統(tǒng)的Postman或者自己寫腳本進(jìn)行最基本的http call(或者curl),我更喜歡使用insomnia這個(gè)更為優(yōu)雅的工具宜猜。而在此之上泼返,它還非常好地支持了GraphQL,這讓我的開(kāi)發(fā)和測(cè)試體驗(yàn)變得更好了姨拥。(Postman至今還不支持GraphQL绅喉,雖然本質(zhì)上我們可以用它make GraphQL query call)

5個(gè)不用GraphQL的理由

  1. 遷移成本
  2. 犧牲Performance
  3. 缺乏動(dòng)態(tài)類型
  4. 簡(jiǎn)單問(wèn)題復(fù)雜化
  5. 緩存能解決很多問(wèn)題

1 使用與遷移成本

現(xiàn)有的RESTful Application如果要改造成GraphQL Application?

hmmm...

我們需要三思叫乌。首先我就不說(shuō)RESTful本來(lái)從end to end都有成熟高效解決方案這樣的廢話了霹疫。遷移的主要問(wèn)題在于,它從根本上改變了我們組織并暴露數(shù)據(jù)的方式综芥,也就是說(shuō)對(duì)于application本身丽蝎,從數(shù)據(jù)層到業(yè)務(wù)邏輯層,可能有極其巨大的影響膀藐。所以它非常不適合現(xiàn)有的復(fù)雜系統(tǒng)“先破后立”屠阻。一個(gè)跑著SpringMVC的龐大Web Application如果要改成時(shí)髦的GraphQL應(yīng)用?這個(gè)成本和破壞性難以預(yù)計(jì)额各。

并且国觉,盡管我們說(shuō)GraphQL有著很好的社區(qū)支持,但本質(zhì)上使用GraphQL虾啦,就等于要使用React與NodeJS麻诀。所以如果并不是正在使用或者計(jì)劃使用React和Node,GraphQL是不適合的傲醉。

2 犧牲Performance

Performance這件事是無(wú)數(shù)人所抱怨的蝇闭。如同我們前面所說(shuō)的,GraphQL的解決方案硬毕,相當(dāng)于把復(fù)雜性和heavy lifting從用戶的眼前呻引,移到了后端——很多時(shí)候,就是數(shù)據(jù)庫(kù)吐咳。

要討論這一點(diǎn)逻悠,我們首先要提的是薇正,為了支持GraphQL queries對(duì)于數(shù)據(jù)的查詢累榜,開(kāi)發(fā)者需要編寫resolvers涮坐。

比如說(shuō)這樣一個(gè)schema:

type Query {
  human(id: ID!): Human
}

type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  name: String
}

對(duì)于human蟋定,我們就需要一個(gè)最基礎(chǔ)的resolver:

Query: {
  human(obj, args, context, info) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

當(dāng)然這還沒(méi)完,對(duì)不同的請(qǐng)求類型臣镣,我們要寫不同的resolver——不僅原來(lái)REST API的CRUD我們都要照顧到犯祠,可能還要根據(jù)業(yè)務(wù)需求寫更多的resolver矛紫。

這件事情造成的影響,除了開(kāi)發(fā)者要寫大量boilerplate code以外撵渡,還可能導(dǎo)致查詢性能低下融柬。一個(gè)RESTful Application死嗦,由于每個(gè)API的確定性趋距,我們可以針對(duì)每一個(gè)API的邏輯,非常好的優(yōu)化它們的性能越除,所以就算存在一定程度的overfetching/underfetching节腐,前后端的性能都可以保持在能夠接受的范圍內(nèi)。然而想要更普適性一些的GraphQL摘盆,則可能會(huì)因?yàn)橐粋€(gè)層級(jí)結(jié)構(gòu)復(fù)雜而且許多域都有很大數(shù)據(jù)量的query跑許多個(gè)resolvers翼雀,使得數(shù)據(jù)庫(kù)的查詢性能成為了瓶頸。

3 缺乏動(dòng)態(tài)類型

強(qiáng)類型的schema固然很省力孩擂,但是如果我們有時(shí)候想要一些自由(flexibility)呢狼渊?

比方說(shuō),有時(shí)候請(qǐng)求數(shù)據(jù)時(shí)类垦,請(qǐng)求方并不打算定義好需要的所有層級(jí)結(jié)構(gòu)和類型與域狈邑。比方說(shuō),我們想要單純地打印一些數(shù)據(jù)蚤认,或者獲取一個(gè)user的一部分fields直接使用米苹,剩下部分保存起來(lái)之后可能使用可能不使用,但并不確定也不關(guān)心剩下的部分具體有那些fields——多余的部分可能作為additional info砰琢,有些域如果有則使用蘸嘶,沒(méi)有則跳過(guò)。

這只是一個(gè)例子陪汽,但是并不是一個(gè)鉆牛角尖的例子——因?yàn)橛袝r(shí)候我們所要的objects的properties本來(lái)就可能是dynamic的训唱,我們甚至可能會(huì)通過(guò)它的properties/fields來(lái)判定它是一個(gè)怎樣的object。

我們要怎么處理這種問(wèn)題呢挚冤?一種有些荒誕現(xiàn)實(shí)主義的做法是雪情,往Type里加一個(gè)JSON string field,用來(lái)提供其相關(guān)的所有信息你辣,這樣就可以應(yīng)對(duì)這種情況了巡通。但是這是不是一個(gè)合理的做法呢?

4 簡(jiǎn)單問(wèn)題復(fù)雜化

最顯著的例子舍哄,就是error handling宴凉。REST API的情況下,我們不需要解析Response的內(nèi)容表悬,只需要看HTTP status code和message弥锄,就能知道請(qǐng)求是否成功,大概問(wèn)題是什么,處理錯(cuò)誤的程序也十分容易編寫籽暇。

然而GraphQL的情景下温治,hmmm...

只要Service本身還在正常運(yùn)行,我們就會(huì)得到200的HTTP status戒悠,然后需要專門檢查response的內(nèi)容才知道是否有error:

 {
      "errors": [
        {
          "message": "Field \"name\" must not have a selection since type \"String\" has no subfields.",
          "locations": [
            {
              "line": 31,
              "column": 101
            }
          ]
        }
      ]
    }

Another layer of complexity.

同時(shí)熬荆,簡(jiǎn)單的Application,使用GraphQL其實(shí)是非常麻煩的——比如前面提到的resolvers绸狐,需要大量的boilerplate code卤恳。另外,還有各種各樣的Types, Queries, Mutators, High-order components需要寫寒矿。相比之下突琳,反倒是REST API更好編寫和維護(hù)。

5 緩存能解決很多問(wèn)題

編寫過(guò)HTTP相關(guān)程序之后應(yīng)該都知道符相,HTTP本身就是涵蓋caching的拆融,更不要提人們?yōu)榱颂岣逺ESTful Application的performance而針對(duì)緩存作出的種種努力。

對(duì)于overfetching和請(qǐng)求次數(shù)冗余的問(wèn)題啊终,假設(shè)我們的整個(gè)application做了足夠合理的設(shè)計(jì)镜豹,并且由于REST API的固定和單純性,緩存已經(jīng)能非常好地減少大量的traffic孕索。

然而如果選擇使用GraphQL逛艰,我們就沒(méi)有了那么直白的caching解決方案。首先搞旭,只有一個(gè)API endpoint的情況下散怖,每個(gè)query都可能不同,我們不可能非常輕松地對(duì)request分門別類做caching肄渗。當(dāng)然并不是說(shuō)真的沒(méi)有現(xiàn)成的工具镇眷,比如說(shuō)Appollo client就提供了InMemoryCache并且,不論有多少queries翎嫡,總是有hot queries和cold ones欠动,那么pattern總是有的。針對(duì)一些特定的query我們還可以定向地緩存惑申,比如說(shuō)PersistGraphQL便是這樣一個(gè)工具具伍。然而這樣做其實(shí)又是相當(dāng)于從queries中提煉出類似于原來(lái)的REST API的部分了,并且又增加了一層complexity圈驼,不管是對(duì)于開(kāi)發(fā)還是對(duì)于performance人芽,這都可能有不容忽視的影響。

總結(jié)

GraphQL最大的優(yōu)勢(shì)绩脆,就是它能夠大大提高開(kāi)發(fā)者的效率萤厅,而且最大化地簡(jiǎn)化了前端的數(shù)據(jù)層的復(fù)雜性橄抹,并且使得前后端對(duì)數(shù)據(jù)的組織觀點(diǎn)一致。只是使用時(shí)惕味,需要考察scale, performance, tech stack, migration等等方面的要求楼誓,做合理的trade-off,否則它可能不僅沒(méi)能提高開(kāi)發(fā)者效率名挥,反倒制造出更多的問(wèn)題疟羹。

References

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躺同,隨后出現(xiàn)的幾起案子阁猜,更是在濱河造成了極大的恐慌丸逸,老刑警劉巖蹋艺,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異黄刚,居然都是意外死亡捎谨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門憔维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涛救,“玉大人,你說(shuō)我怎么就攤上這事业扒〖爝海” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵程储,是天一觀的道長(zhǎng)蹭沛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)章鲤,這世上最難降的妖魔是什么摊灭? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮败徊,結(jié)果婚禮上帚呼,老公的妹妹穿的比我還像新娘。我一直安慰自己皱蹦,他們只是感情好煤杀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著沪哺,像睡著了一般沈自。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凤粗,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天酥泛,我揣著相機(jī)與錄音今豆,去河邊找鬼。 笑死柔袁,一個(gè)胖子當(dāng)著我的面吹牛呆躲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捶索,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼插掂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腥例?” 一聲冷哼從身側(cè)響起辅甥,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎燎竖,沒(méi)想到半個(gè)月后璃弄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡构回,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年夏块,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纤掸。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脐供,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出借跪,到底是詐尸還是另有隱情政己,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布掏愁,位于F島的核電站歇由,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏托猩。R本人自食惡果不足惜印蓖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望京腥。 院中可真熱鬧赦肃,春花似錦、人聲如沸公浪。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欠气。三九已至厅各,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間预柒,已是汗流浹背队塘。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工袁梗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憔古。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓遮怜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鸿市。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锯梁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)焰情,斷路器陌凳,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 知乎連接 本文翻譯自《React 學(xué)習(xí)之道》 原作者 Robin 的一片文章 https://www.robinw...
    azzgo閱讀 15,304評(píng)論 1 14
  • —“我是你的手和腳谒获,這點(diǎn)沒(méi)有異議吧?” —“是的蛤肌,我同意壁却∨” ——《觸不可及》 菲利普的朋友曾告誡他說(shuō):“要擔(dān)心,...
    你家王叔閱讀 548評(píng)論 0 1
  • 為什么很多年輕人不愿意自己做飯? 懶盐肃、麻煩爪膊、沒(méi)時(shí)間……能找出一堆理由。反正能叫外賣砸王,又餓不死推盛。 所長(zhǎng)最近調(diào)節(jié)飲食,...
    云云不語(yǔ)閱讀 1,234評(píng)論 0 5
  • 這個(gè)學(xué)期的第一次辯論即將開(kāi)始了,這是我第一次參加驹闰。少年應(yīng)該有夢(mèng)想和少年應(yīng)該沒(méi)有夢(mèng)想瘪菌。自從楊老師給我們布置了這...
    mikezheng_e830閱讀 178評(píng)論 0 0