Details - Clean Architecture 讀后感 - Part 3

Database

我們已經(jīng)想不起來(lái)為什么需要在項(xiàng)目中使用數(shù)據(jù)庫(kù)了蜕乡,基本上做后端開(kāi)發(fā)是無(wú)法離開(kāi)的數(shù)據(jù)庫(kù)的凯砍,從 RDBMS 到 NoSQL 再到最近火熱的 NewSQL见剩,我們一直在尋找更好更快的數(shù)據(jù)存儲(chǔ)方案剃法,不論硬盤(pán)是從磁盤(pán)變成了 SSD唇礁,但是依舊在用來(lái)自 1970 年代的 SQL勾栗。的確,使用現(xiàn)代的 web 框架配合 MySQL 能夠很快的完成一個(gè) RESTful 或者 MVC 式的應(yīng)用垒迂,但是這是有限的械姻,對(duì)于 RDBMS 來(lái)說(shuō),最大的問(wèn)題是按照行列的方式進(jìn)行數(shù)據(jù)存儲(chǔ)机断,而我們的業(yè)務(wù)邏輯算法(可能只是一個(gè)簡(jiǎn)單的 flatmap 之類(lèi)的東西)楷拳,往往是有多種數(shù)據(jù)結(jié)構(gòu)需求的。

現(xiàn)代數(shù)據(jù)庫(kù)系統(tǒng)都提供了企業(yè)級(jí)的功能吏奸,比如自動(dòng)備份欢揖、落盤(pán)加密、分布式與性能保證奋蔚,對(duì)于架構(gòu)師來(lái)說(shuō)她混,這些寫(xiě)在白皮書(shū)上的功能很有誘惑力,很多時(shí)候我們必須要通過(guò)這些功能點(diǎn)進(jìn)行技術(shù)選型泊碑,但是這是 low-level details坤按,對(duì)于架構(gòu)的設(shè)計(jì)來(lái)說(shuō),我們不應(yīng)該限定某種數(shù)據(jù)庫(kù)馒过,或者并不希望數(shù)據(jù)庫(kù)束縛住我們臭脓。其實(shí)這在項(xiàng)目中還是比較常見(jiàn)的,經(jīng)常會(huì)有人使用 h2 做 memory database 支持開(kāi)發(fā)腹忽,線上則用 MySQL 或者 MariaDB来累,換 JDBC Driver 就可以做到這一點(diǎn)砚作,但是本文是想討論在 JDBC 之外的代碼。

我不止一次的聽(tīng)過(guò)有人說(shuō)嘹锁,使用 MongoDB 是因?yàn)槠涮峁┑男阅芎迹珜?shí)際上優(yōu)化與重構(gòu)代碼所帶來(lái)的性能提升遠(yuǎn)遠(yuǎn)大于使用某種數(shù)據(jù)庫(kù),或者說(shuō)起性能的要求并沒(méi)有極端到必須要使用某種存儲(chǔ)技術(shù)领猾。這并不是說(shuō)性能不重要米同,而是說(shuō)我們的系統(tǒng)應(yīng)該有足夠的靈活性。往往在新項(xiàng)目開(kāi)始時(shí)瘤运,我們所面對(duì)的性能壓力并不是很大窍霞,可能第一個(gè)版本只是需要一個(gè)恰好能夠表達(dá)業(yè)務(wù)就行,這時(shí)候?qū)⒋鎯?chǔ)技術(shù)與實(shí)際業(yè)務(wù)進(jìn)行解耦就非常關(guān)鍵了拯坟,因?yàn)樵诓痪玫膶?lái)可能你會(huì)使用 MySQL Proxy 進(jìn)行分庫(kù)分表但金,或者直接使用 DynamoDB 這種 NoSQL。

有前輩曾向我展示過(guò)某銀行使用 MySQL Binlog 來(lái)進(jìn)行某種數(shù)據(jù)同步郁季,并圍繞該實(shí)現(xiàn)進(jìn)行了一系列的定制化開(kāi)發(fā)冷溃,隨著人員的更迭與技術(shù)的革新,這部分的系統(tǒng)已經(jīng)沒(méi)有人能進(jìn)行修改與升級(jí)了梦裂,誠(chéng)然他們也想換掉這種不是很專(zhuān)業(yè)的實(shí)現(xiàn)似枕,使用專(zhuān)業(yè)的中間件實(shí)現(xiàn)數(shù)據(jù)遷移同步并不是很難,但是已有的代碼與細(xì)節(jié)綁定的太深年柠,以至于沒(méi)人敢停掉現(xiàn)在的實(shí)現(xiàn)凿歼,雖然這些代碼依舊在工作,但是從架構(gòu)的角度上來(lái)說(shuō)是失敗的冗恨。

我們?cè)趯W(xué)習(xí)算法與數(shù)據(jù)結(jié)構(gòu)時(shí)答憔,使用過(guò)很多例如列表、樹(shù)掀抹、鏈表虐拓、隊(duì)列、堆棧傲武、Map 等等很多數(shù)據(jù)架構(gòu)蓉驹,而 RDBMS 只能提供行列,你有沒(méi)有將一個(gè)樹(shù)存放在數(shù)據(jù)庫(kù)中的經(jīng)驗(yàn)揪利?不管使用哪種方式實(shí)現(xiàn)态兴,你都會(huì)覺(jué)得比較別扭,比如下面的例子:

Option 1: 使用 Parent Id 存儲(chǔ)樹(shù)

id | parent_id | data
---+-----------+----------
 1 |      NULL | root
 2 |         1 | Child 1
 3 |         2 | Child 1.1
 4 |         2 | Child 1.2
 5 |         1 | Child 2
 6 |         5 | Child 2.1
 7 |         5 | Child 2.2

Option 2: 存儲(chǔ)左右子樹(shù)的關(guān)系

id | parent_id | lft | rgt | data
---+-----------+-----+-----+----------
 1 |         0 |   1 |  14 | root
 2 |         1 |   2 |   7 | Child 1
 3 |         2 |   3 |   4 | Child 1.1
 4 |         2 |   5 |   6 | Child 1.2
 5 |         1 |   8 |  13 | Child 2
 6 |         5 |   9 |  10 | Child 2.1
 7 |         5 |  11 |  12 | Child 2.2

我個(gè)人曾經(jīng)實(shí)踐過(guò)使用 Option 2 的方式疟位,比如使用類(lèi)似的 SQL 進(jìn)行獲取節(jié)點(diǎn)計(jì)算:

SELECT * FROM nodes WHERE lft >= 2 AND rgt < 7 AND id != 2

這種模式很好的表達(dá)了樹(shù)的結(jié)構(gòu)诗茎,但是在我進(jìn)行節(jié)點(diǎn)的新建與更新時(shí),需要更新的數(shù)據(jù)行要遠(yuǎn)遠(yuǎn)多于 Option 1 了,這種 case 只適用于讀遠(yuǎn)遠(yuǎn)大于寫(xiě)的場(chǎng)景敢订。

Closure Table Pattern 應(yīng)該是最流行的解決方案了,常見(jiàn)在各種 ORM 中會(huì)有實(shí)現(xiàn)罢吃,這里就不展開(kāi)講了楚午。)

這種別扭還會(huì)表達(dá)在其他地方,在代碼中尿招,插入數(shù)據(jù)我們希望是一個(gè) LinkedList矾柜,存儲(chǔ) K-V 我們希望有一個(gè) map,計(jì)算多級(jí)關(guān)系我們希望用樹(shù)就谜,計(jì)算路徑我們需要 graph怪蔑,做 workflow 我們更想要一個(gè) DAG,這些方便的數(shù)據(jù)結(jié)構(gòu)往往在存儲(chǔ)時(shí)會(huì)造成麻煩丧荐,這是我們不得不面對(duì)的缆瓣。

關(guān)系型數(shù)據(jù)庫(kù)的優(yōu)勢(shì)是 relation,使用 relation 你可以很方便的表達(dá)類(lèi)似于我有幾臺(tái)電腦這種關(guān)系虹统,使用 JOIN 類(lèi)似的查詢(xún)也很簡(jiǎn)單弓坞,但這個(gè)功能不一定是必要的。一旦沒(méi)有使用好 JOIN 或者其他的多表查詢(xún)會(huì)造成性能上的問(wèn)題车荔,而且多表查詢(xún)的語(yǔ)句也很難寫(xiě)渡冻、難以理解與改動(dòng)。在你使用 DynamoDB 或者 MongoDB 這種沒(méi)有 relation 的數(shù)據(jù)庫(kù)時(shí)忧便,很多時(shí)候你不得不自己編寫(xiě)程序來(lái)應(yīng)對(duì) JOIN 的需求族吻,對(duì)于 NoSQL 類(lèi)型的數(shù)據(jù)庫(kù),你必須要根據(jù)查詢(xún)需求進(jìn)行性能優(yōu)化珠增,加上分頁(yè)與排序就更糟糕了超歌。

這里面必須提到微服務(wù)的一些特性,在微服務(wù)世界中切平,我們提倡每個(gè)服務(wù)管理自己的數(shù)據(jù)握础,某種程度上降低了多表聯(lián)查的出現(xiàn)(我已經(jīng)很少看到三個(gè)表 JOIN 在一起的語(yǔ)句了,也有規(guī)范認(rèn)為三表 JOIN 就是反模式)悴品,但是并不代表這種 JOIN 的邏輯消失了禀综,當(dāng)你的 BFF 組織后端幾個(gè)服務(wù)的返回時(shí),那其實(shí)就是做 JOIN苔严。

所以定枷,不論你的數(shù)據(jù)庫(kù)是否支持 JOIN 或者類(lèi)似的功能,你都要將查詢(xún)的方法與過(guò)程遠(yuǎn)遠(yuǎn)的放到業(yè)務(wù)邏輯之外届氢,就是上文中我們畫(huà)過(guò)的邊界欠窒。你的 service 希望 repository 提供什么樣的數(shù)據(jù),這在接口上一定要定義清楚,有時(shí)候你甚至需要再引入 converter 將格式化的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)為適合計(jì)算的數(shù)據(jù)結(jié)構(gòu)岖妄⌒徒看起來(lái)是麻煩了一些,但是邊界清晰荐虐,即使存儲(chǔ)層有巨大的改動(dòng)需求七兜,也不需要破壞與改動(dòng) service。

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.UUID;

public interface BookRepository extends JpaRepository<Book, UUID> {
    Page<Book> findByBookIdOrderByUpdatedSeqDesc(String userId, Pageable pageable);

    Optional<Book> findByIdAndUserId(UUID bookId, String userId);

    Optional<Book> findByNameAndUserId(String bookName, String userId);
}

ORM 也是現(xiàn)代框架中提供的殺手級(jí)功能福扬,的確能夠滿(mǎn)足大多數(shù)人的開(kāi)發(fā)需求腕铸,但是我們也要警惕 ORM 過(guò)多侵入代碼的問(wèn)題。Uncle Bob 不提倡我們將 ORM 的 Model 與業(yè)務(wù)的 Entity 混在一起铛碑,但是對(duì)于 Spring 開(kāi)發(fā)的程序員來(lái)說(shuō)很難做到狠裹,因?yàn)?Annotation Entity 幾乎隨處可見(jiàn),特別是 relation 的注解可以讓我們不需要直面 JOIN 語(yǔ)句了汽烦,還有其他的注解例如 org.hibernate.annotations.Type 這種直接幫我們使用數(shù)據(jù)庫(kù)的功能的涛菠。但是其實(shí)也沒(méi)那么糟糕,對(duì)于 Java Annotation 來(lái)說(shuō)刹缝,你不使用它就可以忽略它碗暗,只是不太好看,但是其他的編程語(yǔ)言與框架就不一定了梢夯。

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import java.time.Instant;
import java.util.UUID;

@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"type", "value"})})
public class FakeProductExample {
    @Id
    @Type(type = "pg-uuid")
    private UUID id;

    @Column
    private String type;

    @Column
    private String value;
    @CreationTimestamp
    @Column(updatable = false)
    private Instant createdAt;

    @UpdateTimestamp
    private Instant updatedAt;

...

所以我的感受是言疗,在 ORM 層與數(shù)據(jù)庫(kù)細(xì)節(jié)決裂是還是可以實(shí)現(xiàn)的,從 Service 層來(lái)看颂砸,是不需要知道 Entity 的 Annotation噪奄,把 Entity 做 POJO 來(lái)使用就可以了,同時(shí)再直接使用 Repository 獲取 Entity人乓,因?yàn)?Repository 是接口勤篮,對(duì)于 Service 來(lái)說(shuō)就無(wú)須考慮存儲(chǔ)細(xì)節(jié)了。在其他語(yǔ)言或者框架下色罚,這也是可以做到的碰缔,比如下面這個(gè) scala 的例子:

trait ProfilesDAO extends BasicDAO {
  def createProfile(profileUid: String, ...): Profile
  def updateProfileEmail(uid: String, ...): Int
  def profileEmailExists(email: String): Boolean
...
}


object ProfileDAOImpl extends ProfilesDAO with DBPreferenceSupport with PrimitiveTypeMode {
  def trans[A](action: => A): A = transaction(action)

  override createProfile(profileUid: String, ...): Profile = {
    ...
    inTransaction {
      DB.profile.insert(profile)
      ...
    }
    profile
  }
...
}

import SquerylImplicits._
object DB extends Schema {
  case class Profile(
                      @Column("profile_uid") id: String,
                      ...
                      @Column("profile_name") name: Option[String],
                      ...
                      @Column("updated_at") updatedAt: Timestamp = new Timestamp(System.currentTimeMillis())
                      ) extends KeyedEntity[String] {
    def uid: String = id
  }
...
}

trait ProfilesDAO 是外部代碼使用的邊界,就相當(dāng)于我們常說(shuō)的接口戳护,ProfileDAOImpl 負(fù)責(zé)實(shí)現(xiàn)金抡,調(diào)用了 DB 的成員( object DB 很輕量,寫(xiě)了一些映射就足夠了)并混入了其他功能腌且,我個(gè)人還是比較喜歡這種做法的梗肝,沒(méi)有引入過(guò)重的依賴(lài),邊界也很清晰铺董,ProfileDAOImpl 的實(shí)現(xiàn)也比較符合語(yǔ)言特性巫击。

Web

在我剛開(kāi)始工作的時(shí)候,我接觸了 jQuery 并很開(kāi)心的使用 $.ajax 去調(diào)用一些 endpoint,幾年后我很驚訝的發(fā)現(xiàn)我自己竟然都沒(méi)意識(shí)到我是在進(jìn)行 web service 調(diào)用坝锰,因?yàn)?web service 并不只是笨重的 WSDL 與 SOAP 按饫痢!隨著時(shí)代的發(fā)展什黑,MVC 式的應(yīng)用已經(jīng)逐漸沒(méi)落崎淳,大家已經(jīng)不喜歡做一個(gè)大而笨重的后端應(yīng)用了,也不喜歡既有后端渲染頁(yè)面愕把,也有異步的 ajax 去實(shí)現(xiàn)的復(fù)雜前端。HTML5 的發(fā)展伴隨著單頁(yè)應(yīng)用的崛起森爽,而我們的后端從有狀態(tài)恨豁、有 session 的巨大單體應(yīng)用也逐漸解體,被微服務(wù)替代爬迟,我覺(jué)得挺好橘蜜,很高興我們不是在瀏覽器里跑 Java Applet 或者 Microsoft Silverlight 了。

Framework

按照 Uncle Bob 的說(shuō)法 Framework 也是一種 detail付呕,我們要避免自己的應(yīng)用被框架綁架的情況计福。在現(xiàn)代軟件開(kāi)發(fā)的過(guò)程中,我們無(wú)法不依賴(lài)前人進(jìn)行開(kāi)發(fā)徽职,往往的節(jié)奏是象颖,選定一個(gè)框架,搞清楚基本功能姆钉,然后在上實(shí)現(xiàn)我們想要的功能说订。我們會(huì)跟進(jìn)應(yīng)用程序的需求來(lái)選擇框架,比如說(shuō)在 web 領(lǐng)域潮瓶,如果是一個(gè) MVC 式的應(yīng)用可能會(huì)使用 Spring MVC 或者 Ruby on Rails陶冷,如果我們要寫(xiě) Restful Service,可能會(huì)用 Ruby grape毯辅、Spring Boot埂伦、Scala Unfiltered 等等。因?yàn)榭蚣艿拇_幫助我們提供了通用的功能思恐,幫助我們可以專(zhuān)注于業(yè)務(wù)邏輯沾谜。但是往往的問(wèn)題是,我們對(duì)框架的依賴(lài)過(guò)重了壁袄,導(dǎo)致我們的代碼與框架沒(méi)有清晰的邊界类早,從而失去擴(kuò)展的機(jī)會(huì)。

import unfiltered.netty.future.Plan._
import unfiltered.request._

class AppRoutes(myController: MyController) {

  val routes: Intent = {
    case req@GET(Path(Seg("schema" :: Nil)))     => myController.schema()
    case req@GET(Path(Seg("search" :: Nil)))     => myController.search(req)
...
    case req@OPTIONS(Path(Seg("search" :: Nil))) => myController.options(req)
  }

}

上面的例子描述了嗜逻,在路由中我們調(diào)用了 myController 來(lái)處理業(yè)務(wù)涩僻,而 myController 是注入進(jìn)來(lái)的,我們沒(méi)有在路由中直接編寫(xiě)業(yè)務(wù)邏輯。但是逆日,myController.search(req) 方法中的 req 是框架所定義的 HttpRequest嵌巷,所以 MyController 的必須要依賴(lài) unfiltered.request.HttpRequest,那么你就無(wú)法輕松的換掉 unfiltered 了室抽。

這種被綁架的情況被稱(chēng)為“不對(duì)稱(chēng)的婚姻”搪哪,對(duì)于 framework 的發(fā)明者來(lái)說(shuō),這樣的方式的確易于控制坪圾,但是對(duì)你來(lái)說(shuō)晓折,你必須適應(yīng)框架的規(guī)則,并且持續(xù)更新兽泄,你必須要做出很大的改動(dòng)才能適應(yīng)和使用框架漓概。我們?cè)谑褂?play framework 時(shí)就遇見(jiàn)了類(lèi)似的問(wèn)題,該項(xiàng)目是接手一個(gè)無(wú)人管理的代碼倉(cāng)庫(kù)病梢,為了升級(jí) framework 我們必須做出上千行的代碼改動(dòng)胃珍,危險(xiǎn)的是,因?yàn)闆](méi)有足夠的上下文蜓陌,沒(méi)人能確定改動(dòng)的正確性觅彰,更悲觀的是,因?yàn)槭褂昧撕芏嗵匦裕ㄈ罩九ト取⒙酚商钐А⒆⑷氲龋覀儙缀鯚o(wú)法選擇新的框架來(lái)替代霉旗。

另一個(gè)例子是痴奏,某公司的新一代交易引擎是跑在 Flink 這種流式處理框架之上的,幾個(gè)運(yùn)算模塊按照拓?fù)浔灰来握{(diào)用厌秒,對(duì)于這個(gè)場(chǎng)景他們使用 Flink 只是為了先驗(yàn)證計(jì)算方式的正確性读拆,而未來(lái)到底使用哪種技術(shù)去調(diào)用引擎還不是很確定(可以理解為這是一個(gè)從消息中間件中獲取事件,再調(diào)用引擎計(jì)算鸵闪,再將計(jì)算結(jié)果放回中間件檐晕,這種模式非常常見(jiàn))。所以在架構(gòu)的角度上來(lái)說(shuō)蚌讼,F(xiàn)link 與計(jì)算引擎之間的邊界很清晰辟灰,所以在嘗試使用其他消息消費(fèi)者獲取事件、驅(qū)動(dòng)計(jì)算的過(guò)程中篡石,計(jì)算引擎的部分一行代碼都沒(méi)有修改芥喇,整個(gè)改動(dòng)也非常小,在實(shí)際的工作中凰萨,其他忙于引擎開(kāi)發(fā)的同事并沒(méi)有感受到任何變化继控。

上面那個(gè)項(xiàng)目最后使用了 Spring Boot 來(lái)進(jìn)行依賴(lài)注入械馆、組裝 bean,那么不需要 Spring Boot 可以做這件事嗎武通?答案是可行的霹崎,Spring Boot 使用 autowire 的方式來(lái)管理組件的依賴(lài),我們自然也可以使用手動(dòng)管理冶忱。將業(yè)務(wù)需要的 bean 一個(gè)個(gè) new 出來(lái)尾菇,再組裝在一起,看起來(lái)復(fù)雜囚枪,但是實(shí)際上只花了一個(gè)多小時(shí)就做到了派诬,你可以把這些邏輯放在 main 函數(shù)中,而這些邏輯是與框架無(wú)關(guān)的链沼,所以你可以像 helloworld 一樣千埃,點(diǎn)一個(gè)按鈕就可以跑起來(lái)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忆植,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谒臼,更是在濱河造成了極大的恐慌朝刊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜈缤,死亡現(xiàn)場(chǎng)離奇詭異拾氓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)底哥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)咙鞍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人趾徽,你說(shuō)我怎么就攤上這事续滋。” “怎么了孵奶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵疲酌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我了袁,道長(zhǎng)朗恳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任载绿,我火速辦了婚禮粥诫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘崭庸。我一直安慰自己怀浆,他們只是感情好谊囚,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著揉稚,像睡著了一般秒啦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搀玖,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天余境,我揣著相機(jī)與錄音,去河邊找鬼灌诅。 笑死芳来,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猜拾。 我是一名探鬼主播即舌,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挎袜!你這毒婦竟也來(lái)了顽聂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盯仪,失蹤者是張志新(化名)和其女友劉穎紊搪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體全景,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耀石,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爸黄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滞伟。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炕贵,靈堂內(nèi)的尸體忽然破棺而出梆奈,到底是詐尸還是另有隱情,我是刑警寧澤鲁驶,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布鉴裹,位于F島的核電站,受9級(jí)特大地震影響钥弯,放射性物質(zhì)發(fā)生泄漏径荔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一脆霎、第九天 我趴在偏房一處隱蔽的房頂上張望总处。 院中可真熱鬧,春花似錦睛蛛、人聲如沸鹦马。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荸频。三九已至菱肖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旭从,已是汗流浹背稳强。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留和悦,地道東北人退疫。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸽素,于是被迫代替她去往敵國(guó)和親褒繁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 架構(gòu)與架構(gòu)師 對(duì)于軟件產(chǎn)品來(lái)說(shuō)馍忽,往往是用兩方面的價(jià)值體現(xiàn)棒坏,行為與結(jié)構(gòu),行為方面的價(jià)值表現(xiàn)為業(yè)務(wù)上的實(shí)現(xiàn)遭笋,也就是你可...
    張羽辰閱讀 644評(píng)論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 今天讀完書(shū)了俊抵。《房思琪的初戀樂(lè)園》好像時(shí)空的爪子從深淵里拽出來(lái)坐梯。 她覺(jué)得不是學(xué)文學(xué)的人,而是文學(xué)辜負(fù)了她刹帕。世界的背...
    橘子沒(méi)汽水閱讀 122評(píng)論 0 0
  • 2018年1月20日臘月初四 距離寶寶出生還有46天 再也容忍不了自己這樣思考與行為的習(xí)慣:反思了自己的歸納整理學(xué)...
    大臉貓左左閱讀 261評(píng)論 0 0
  • 兵馬俑是世界八大奇跡之一吵血,被聯(lián)合國(guó)教科文組織列為世界遺產(chǎn),聞名程度及信息介紹無(wú)需累贅偷溺。很多人抱著感受震撼的...
    靜水覺(jué)閱讀 953評(píng)論 2 3