Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Boost Dev and App Performance

Boost Dev and App Performance

2019/07/31 LINE Developer Meetup 開發者小聚系列活動 #9 https://linegroup.kktix.cc/events/20190731

LINE Developers Taiwan

July 31, 2019
Tweet

More Decks by LINE Developers Taiwan

Other Decks in Programming

Transcript

  1. • 4DIFEVMF NPOUIT XPSLEBZT • 3FTPVSDFT1.Y'&Y#&Y69 Y2"Y • 4FSWJDFT •

    6TFSXFCTJUF • 1BSUOFSXFCTJUF $.4  • $IBUCPU • *OUFSOBM$.4 New Service
  2. • ) • *ONFNPSZ%# • "VUPNBUJDUBCMFDSFBUJPOPO&OUJUZ • *OJUJBMEBUBMPBEJOHUISPVHIˊ JNQPSUTRM •

    spring.jpa.generate-ddl: true • spring.jpa.hibernate.ddl-auto: create • .ZTRM • $SFBUFBMUFSUBCMFNBOVBMMZ Database
  3. @Data @Entity public class Article implements Serializable { private long

    reporterCount; private long followerCount; @Formula("reporter_count + follower_count") private long hottestCount; @OneToMany(mappedBy = "article", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST, orphanRemoval = true) @JsonIgnoreProperties({"article", "reviewer", "feedbacks"}) private Set<Comment> comments; } Entity
  4. @Data @Entity @JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler", "article", "feedbacks", "createdAt"

    }) public class Comment implements Serializable { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "article_id") @JsonIgnoreProperties({"partner", "comments"}) private Article article; } Entity
  5. @Repository public interface ArticleRepository extends JpaRepository<Article, Long> { @Query("SELECT article

    " + "FROM Article article " + "WHERE language = :language ”) Page<Article> getLatestOrHottestArticle( @Param("language") String language , Pageable page); } Repository
  6. @CrossOrigin public class PublicController { @GetMapping(”/pub/v1/{language}/articles/latest") public ResponseEntity<Page<Article>> getLatestArticles( @PathVariable(name

    = "language") String language , @PageableDefault(size = 20, sort = { "updatedAt" }, direction = Sort.Direction.DESC, page = 0) Pageable pageable ) { return ResponseEntity.ok() .cacheControl(CacheControl.maxAge(fakenewsProperties.getCacheMaxAge(), TimeUnit.SECONDS).cachePublic().sMaxAge(fakenewsProperties.getCacheMaxAge(), TimeUnit.SECONDS)) .body(articleRepository.getLatestOrHottestArticle(language, pageable)); } } Controller
  7. @RunWith(SpringRunner.class) @ActiveProfiles("domain-test") @DataJpaTest public class ArticleRepositoryTests { @Autowired private ArticleRepository

    articleRepository; @Test public void getLatestOrHottestArticleTest() { Pageable page = PageRequest.of(0, 12, Sort.by("createdAt").descending().and(Sort.by("hottestCount"))); Page<Article> articleList = articleRepository. getLatestOrHottestArticle(“zhtw”, page); assertThat(articleList.getTotalElements()).isEqualTo(16); } } Unit Test Repository
  8. @Api(tags = "guest") @GetMapping("/articles/{id}") @ApiOperation(value = "Get article by articleId")

    @ApiImplicitParam(name = "X-Line-Biz-Session", value = "Biz Id Session", required = true, paramType = "header", dataType = "String", example = "ses-from-cookie") public Article getArticleById( @PathVariable long id) { return articleRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Not found")); } Swagger
  9. @RunWith(SpringRunner.class) @ContextConfiguration(classes = WebApplication.class) @ActiveProfiles("test") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class

    PublicControllerIntegrationTests { @LocalServerPort int port; @Before public void setBaseUri () { RestAssured.port = port; } @Test public void getLatestArticlesWithSortTest() { given() .queryParam("sort", "createdAt,asc") .when() .get("/pub/v1/zhtw/articles/latest") .then() .statusCode(OK.value()) .assertThat() .body("content.size()", is(10)) .body("content.id", hasItems(13, 12, 4, 10, 8, 11, 1, 6, 9, 5)) ; } } Integration Test
  10. POST /articles PUT /articles/{articleId} GET /articles/{articleId} DELETE /articles/{articleId} @RepositoryRestResource public

    interface ArticleRepository extends JpaRepository<Article, Long> { Book findOne(Integer id); Page<Book> findAll(Pageable pageable); Book save(Book s); @RestResource(exported = false) void delete(Book t); } Repository Rest Resources
  11. { ”content" : ”Article Content", "_links" : { "self" :

    { "href" : "http://localhost:8080/articles/1" }, ”article" : { "href" : "http://localhost:8080/articles/1" }, ”comment" : { "href" : "http://localhost:8080/articles/1/comments" } } } Repository Rest Resources
  12. @Entity @NamedEntityGraph( name = "graph.Article", attributeNodes = { @NamedAttributeNode("tag"), @NamedAttributeNode("category"),

    @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"), }, subgraphs = { @NamedSubgraph( name = "comments-subgraph", attributeNodes = { @NamedAttributeNode("judgement"), @NamedAttributeNode("partner") } ) }) public class Article implements Serializable { EntityGraph
  13. { "article": [{ "id": 8, "content": "Hello world", "comments": {

    "id": 4, "handler": {}, "hibernateLazyInitializer": {} } ”followers” : {…} }]} HTTP Status 500 - Could not write content: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer @JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler", "article", "feedbacks", "createdAt" }) Entity Caveats
  14. • )JCFSOBUF-FWFM$BDIF • &IDBDIF ˊ -PDBMDBDIF • 3FEJT ˊ %JTUSJCVUFEDBDIF

    • -B[ZMPBEJOHJTTVF • %FTFSJBMJ[BUJPOJTTVF • 3FEJT DMVTUFSTFOUJOFMTVQQPSU Cache Caveats
  15. public interface ArticleBasicProjection { long getId(); Category getCategory(); } @Query("SELECT

    article " + "FROM Article article " + "WHERE article.id = :articleId”) Optional<ArticleBasicProjection> getArticleBasicById( @Param("articleId") Long articleId , @Param("reporterCount") Long reporterCount ); Projection
  16. • 6TJOH3FWFSTF1SPYZPS$%/ • OHJOY SFWFSTFQSPYZ • proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=factchecker:2m

    max_size=256m inactive=12h use_temp_path=off; • mount -t tmpfs -o size=256M tmpfs /var/cache/nginx • ttl = 0 • Max-age: 600 return ResponseEntity.ok() .cacheControl(CacheControl.maxAge(fakenewsProperti es.getCacheMaxAge(), TimeUnit.SECONDS).cachePublic().sMaxAge(fakenewsProperties.getCach eMaxAge(), TimeUnit.SECONDS)) .body(…) Reverse Proxy Cache
  17. API Response Time $POGJH +ECD FYFDUJNF NT 314 3FTQPOTF5JNF NT

    8JUIPVU&OUJUZ(SBQI &OUJUZ(SBQI 8JUIPVU&OUJUZ(SBQI -DBDIF &OUJUZ(SBQI -DBDIF 3FTQPOTFDBDIF  KECD  KECD