[Intellij] GCP Plugin - Cloud Code 사용하기 in GCS | [Intellij] GCP Plugin - Using Cloud Code in GCS
쟈 미
728x90
GCP Storage를 연동하여 사용하는 글을 블로그에 포스팅하려고 Spring initializer을 이용해서 프로젝트를 생성하면서 좋은 플러그인을 발견하여 기록하려 한다.
Initializer에 Google Cloud Platform 관련 dependency를 추가할 수 있다.
위와 같은 dependency로 새로운 프로젝트를 설정하니, Cloud Code라는plugin 추천이 떠서 받아보았다.
Google에서 개발한 플러그인으로, Stackdriver Debugger와 통합되서 GCP에서 실행되는 프로덕션 애플리케이션을 intellij에서 디버깅할 수 있다. 특히 Stackdriver Debugger를 사용하면 Stackdriver Debugger를 사용하면 Compute Engine과 App Engine 표준 환경 및 가변형 환경 모두에 배포된 자바 애플리케이션을 디버깅할 수 있다. 또한 App Engine 환경 배포를 직접 IDE에서 할 수 있는 장점도 있다.
인텔리제이의 Preference [ ⌘ + , ] > Plugins > clod code 를 입력해서 설치할 수 있고, 설치가 완료될 경우 intellij를 restart 해야 플러그인을 적용할 수 있다.
또한 Cloud code에서 제공하는 Google Cloud Platform에서 지원되는 프로덕트 리스트는 총 6개로 아래와 같으며 개요를 읽어보니 Kubernetes 개발, 배포에서의 편리성이 가장 큰 장점인 듯하다. 근데 나는 Storage 작업을 할 건데,, 여튼 저튼 쨋든
1. Kubernetes : 애플리케이션 만들기, 애플리케이션 배포 2. App Engine : 애플리케이션 만들기 (가변, 표준) 3. Cloud API 및 GCP 라이브러리 관리 4. Cloud Storage : 버킷 안 콘텐츠 탐색 5. Cloud Source Repositories 6. Stackdriver Debugger : logging
storage 연동 첫 시작
플러그인 설치를 완료하면 인텔리제이 우측 바에 google cloud storage, kubernetes Explorer 탭이 생기게 되는데, 현재 내가 개발할 프로젝트는 cloud storage와 관련된 것이라, storage 탭에 대해 알아보려 한다.
storage에 대한 정보를 얻어오려면 GCP의 project를 가져와야 하기 때문에, 그 프로젝트의 권한을 가진 account로 로그인을 진행해준다. 로그인 버튼을 누르면 왼쪽 아래 그림과 같이 로그인 창이 뜨고 허용을 눌러주면 된다.
그리고 다시 Google Cloud Storage 탭으로 돌아와서 project를 선택해주면 되는데, 내가 현재 선택한 account가 권한을 가진 프로젝트 리스트들이 보이므로, storage를 사용할 project를 선택한다.
나는 daily-commit이라는 개인 프로젝트를 선택하였다. 이렇게 간단하게 연동이 마무리된다. 따로 console.cloud web을 통해 bucket안에 들어있는 파일, 폴더를 찾아보지 않아도, 개발하는 IDE 안에서 볼 수 있다는 장점이 있다.
나는 프로젝트에 두 개의 bucket을 만들었고, ewha-commiters-static-file이라는 버킷 안에 여러 폴더 + 파일들을 저장해둔 상태이다. 이런 내용을 기존에 GCP console Web을 이용해서 봤다면, 플러그인을 이용해서
아래와 같이 Intellij IDE 안에서 bucket안에 있는 파일의 내용을 확인할 수 있다.
다만 아쉬운 게 있다면, Web GCP Console에서는 파일의 URL 및 다운로드 경로도 알 수 있었는데, plugin에서는 지원하지 않는다. 우클릭을 하면 아래와 같이 blob name과 bucket name을 복사할 수 있는데, 이를 이용해서 수제로 URL, 다운로드 경로를 만들어주자.
storage 버켓 확인은 이 플러그인을 효과적으로 사용하는 것 같지 않아서, 다음번엔 Stackdriver의 log를 플러그인을 통해서 확인하는 방법을 찾아봐야겠다.
I was about to write a blog post about integrating GCP Storage, and while creating a project using Spring Initializer, I discovered a nice plugin that I wanted to document.
You can add Google Cloud Platform related dependencies in the Initializer.
When I set up a new project with the dependencies shown above, a plugin recommendation for Cloud Code popped up, so I gave it a try.
It's a plugin developed by Google that integrates with Stackdriver Debugger, allowing you to debug production applications running on GCP directly from IntelliJ. In particular, with Stackdriver Debugger, you can debug Java applications deployed on both Compute Engine and App Engine standard and flexible environments. It also has the advantage of letting you deploy to App Engine environments directly from the IDE.
You can install it by going to IntelliJ's Preferences [ ⌘ + , ] > Plugins > and searching for "cloud code." Once the installation is complete, you need to restart IntelliJ to apply the plugin.
After reading the documentation, I found out it works not only with IntelliJ but also with several other JetBrains IDEs. However, it is optimized for IntelliJ.
Also, Cloud Code supports a total of 6 Google Cloud Platform products as listed below. After reading the overview, it seems like the biggest advantage is the convenience in Kubernetes development and deployment. But I'm just going to work with Storage, so... anyway, moving on.
Once the plugin installation is complete, you'll see Google Cloud Storage and Kubernetes Explorer tabs appear on the right sidebar of IntelliJ. Since the project I'm currently working on is related to Cloud Storage, I'm going to look into the Storage tab.
To retrieve information about storage, you need to fetch the GCP project, so you'll need to log in with an account that has permissions for that project. When you click the login button, a login window like the one on the left below will appear — just click "Allow."
Then go back to the Google Cloud Storage tab and select your project. You'll see a list of projects that your currently selected account has permissions for, so just pick the project you want to use with Storage.
I selected my personal project called "daily-commit." And just like that, the integration is done. The nice thing is that you don't have to go through console.cloud web to look for files and folders inside your buckets — you can see them right inside the IDE where you're developing.
I created two buckets in my project and have stored several folders and files inside a bucket called "ewha-commiters-static-file." If you used to check this kind of stuff through the GCP Console Web, now you can use the plugin instead.
As shown below, you can view the contents of files inside a bucket right within the IntelliJ IDE.
One downside though — in the Web GCP Console, you could see the file's URL and download path, but the plugin doesn't support that. If you right-click, you can copy the blob name and bucket name as shown below. You can use these to manually construct the URL and download path.
URL : gs://[bucket-name]/[blob-name] Link URL : https://storage.cloud.google.com/[bucket-name]/[blob-name]?authuser=0
Checking storage buckets doesn't seem like the most effective use of this plugin, so next time I should look into how to check Stackdriver logs through the plugin.
크롤링 테스트를 위한 mock server test 구축 | Mock Server Test Setup for Crawling Tests
쟈 미
728x90
Java의 Jsoup을 이용해서 페이지를 크롤링을 하는 코드는 찾아보면 많지만, 크롤링을 테스트하는 코드는 찾기 어려웠다. 따라서 크롤링 테스트를 짜기 위해 직접 nginx 서버를 띄어서 그 서버를 크롤링 하기도 하는 등 JavaBom 스터디원과 구현해본 크롤링 테스트에 대한 포스팅을 하게되었다.
참고로 nginx 서버를 띄어서 크롤링 하는것은 실제 서버라서 크롤링 테스트의 의미가 없는 것 같다. 실제 서버가 죽으면 크롤링 테스트도 못하므로 결국은 @Ignore를 걸어야하는 테스트이기 때문이다.
내가 생각하기에 크롤링 테스트에서 쟁점은 2가지 이다.
1. Parsing 테스트
[ 목적 ] 페이지의 html 파일에서 크롤링을 통해 원하는 정보만을 가져왔는지 체크한다.
[ 구현 방식 ] 크롤링을 원하는 페이지를 ctrl+s 를 이용해서 .html 파일로 받아온 후, 해당 html 파일을 내 springboot project의 resource에 저장한다. 이후 이 파일을 받아와, 내가 만든 크롤링 코드를 돌려서, 내가 파싱한 정보가 원하는 대로 크롤링이 잘 되었는지 확인한다. ex ) 네이버 영화 html 에서 찾아온 영화 리스트의 항목이 10개가 맞는가 ex ) 크롤링 한 영화의 title이 "마션"이 맞는가 등등 (젤 좋아하는 영화ㅋ)
2. connect 테스트
[ 목적 ] 크롤링으로 해당 페이지를 받아오겠다는 요청을 보내고 응답을 받아 내가 만든 크롤링 코드가 잘 동작하는지를 체크한다.
[ 구현방식 ] test를 위한 Mockserver를 하나 생성한다. 우리가 보는 영화 페이지가 GET 메서드로 URL 요청을 보낼 경우 html 페이지를 리턴하는 것처럼, jsoup connect의 결과를 확인하기 위한 mock server를 만드는 것이다. 이 mock server 역시 내가 크롤링을 원하는 네이버 영화 페이지처럼 특정 URL 을 GET 메소드를 통해 호출할 경우 특정 html 파일을 보내도록 지정한다.
[ 기능 ] 1. 고정된 response를 만들고 return 할 수 있다. 2. request를 다른 서버에 forwarding 한다. 3. callbacks 실행이 가능하다. 4. request를 확인할 수 있다.
오늘 포스팅을 위해 사용할 크롤링 코드 + 페이지는 네이버 영화의 랭킹 페이지를 이용하려 한다.
// NaverMovieCrawler.java
public class NaverMovieCrawler {
public static void main(String[] args) {
Document document = new NaverMovieCrawler().getCrawlingResult("https://movie.naver.com/movie/sdb/rank/rmovie.nhn");
TopMovieList topMovieList = new TopMovieList(document);
System.out.println(topMovieList.toString());
}
// url 을 보내면, 해당 페이지를 크롤링하여 Document 타입을 리턴한다.
public Document getCrawlingResult(String url){
try {
return Jsoup.connect(url)
.timeout(2000)
.get();
} catch (IOException e) {
throw new RuntimeException("crawling 실패");
}
}
}
스프링 부트로 API를 만들어도 좋지만, 일단은 간단하게 main을 만들어주고 돌려보았다. 여기서 중요한게 Jsoup.connect(url) 이부분인데, mockserver 테스트를 하는 이유라고 볼 수 있다. Jsoup의 connect() 메소드는 기본적으로 get 매핑을 요청하며, 이외에도 Jsoup에서 제공하는 다양한 메서드를 체이닝해서 좀 더 구체적인 요청을 보낼 수 있다.
그러나 크롤링하려는 네이버 영화 페이지의 html 파일을 받는 요청이므로 Get 메서드 + URL 매핑으로 간단하게 connect() 메소드를 마무리 할 수 있다.
@AllArgsConstructor
@Getter
public class TopMovieList {
private List<Movie> topMovies = new ArrayList<>();
public TopMovieList(Document document){
this.topMovies = document.select("table.list_ranking tr").stream()
.filter(x -> !x.select(".ac").isEmpty())
.map(Movie::of)
.collect(Collectors.toList());
}
@Override
public String toString() {
return topMovies.stream()
.map(Movie::toString)
.collect(Collectors.joining(", \n"));
}
}
main에서 보면 알다싶이 나는 TopMovieList 라는 일급컬렉션 객체를 만들었다. Jsoup을 이용해 받아온 Document객체를 (html 전체를 리턴한다) 파싱하여 List<Movie>를 만든다. 이때 Movie 객체 하나하나를 만들기 위해서 네이버 영화 랭킹의 테이블 row를 하나씩 분리해서 Movie 객체의 of() 메서드를 이용해서 테이블 row 하나하나에서의 데이터를 분리하는 파싱은 Movie 객체에 맡긴다.
// Movie.java
public class Movie {
private int rank;
private String title;
private String detailLink;
@Builder
private Movie(int rank, String title, String detailLink) {
this.rank = rank;
this.title = title;
this.detailLink = detailLink;
}
public static Movie of (Element element){
return Movie.builder()
.rank(Integer.parseInt(element.select(".ac img").attr("alt")))
.title(element.select(".title").text())
.detailLink(element.select(".title a").attr("href"))
.build();
}
@Override
public String toString() {
return "Movie{" +
"rank='" + rank + '\'' +
", title='" + title + '\'' +
", detailLink='" + detailLink + '\'' +
'}';
}
}
Movie 객체에서는 List<Movie>의 생성자에서 받아온 네이버 영화 랭킹 테이블의 row 한 줄인 Element를 인자로 받아, 데이터를 바인딩해준다. 개발자도구를 이용해 내가 원하는 데이터만을 분리할 수 있도록 css selector를 적절히 사용한다.
가장 먼저 크롤링 하려는 네이버 영화 페이지로 가서 html을 다운받는다. 내가 request와 response를 지정한 mock-server가 네이버 영화 페이지의 html 파일을 리턴해야하기 때문이다.
나는 ranking_naver_move.html 이라는 이름으로 저장했다. 그리고 이렇게 다운 받은 파일을 test > resources 폴더 안에 넣는다. 추가로 초반에 말한 parsing test와 관련해서 테스트 코드를 구현하느라 나는 하나의 html 파일이 더 생기게되었다.
mock-server와 관련한 테스트를 할 경우에, 테스트 시작 전, mock-server를 열어주어야한다. 그리고 테스트가 끝나면 mock-server를 닫아주어야한다. 따라서 테스트코드의 시작과 전에 해당 메소드를 써준다.
// NaverMovieCrawlerTest.java
public class NaverMovieCrawlerTest {
private static final int PORT = 9000;
private static ClientAndServer mockServer;
@BeforeEach
void setUp() {
mockServer = ClientAndServer.startClientAndServer(PORT);
System.out.println("mock server start");
}
@AfterEach
void tearDown() {
mockServer.stop();
System.out.println("mock server stop");
}
@Test 어노테이션을 붙인 빈 테스트를 만들고 이 테스트를 돌려보면 아래와 같이 엄청난 로그가 뜬다. 몇몇 눈에 띄는 로그를 읽어보면 java version, cachesize 등을 자동으로 설정함을 알 수 있다.
또한, 인자로 PORT값 즉 9000을 넣은 것은 잠깐 뜨는 mock-server의 포트를 지정한 것인데, 실제로 sout을 찍은 부분 이후 즉, startClientAndServer 메소드가 실행된 이후, 9000포트로 mock-server가 설정되었다는 로그가 출력됨을 알 수 있다.
이제 mock-server의 자세한 세팅을 해보자. 내가 구현하려는 mock-server는 네이버 영화 랭킹 페이지와 같은 역할을 수행해야한다. 따라서 네이버 랭킹 페이지를 나타내는 path인 "/movie/sdb/rank/rmovie.nhn"을 입력할 경우 html 파일을 리턴하도록 mock-server의 기능을 설정해주어야 한다. 이는 아래 보다 싶이 .when()과 .response() 메소드를 이용해서 설정할 수 있다.
when 부분에는 mock-server에서 받을 request 그리고 respond 부분에는 when에서 정의한 request를 받았을 때 실행할 response 값을 설정해 준다.
가장 먼저 mock-server host인 localhost, 그리고 mock-server를 실행할 port를 적어준다. 이때 mock-server를 가장먼저 시작할 때 사용하는 메서드인 ClientAndServer.startClientAndServer() 에서 만든 mockserver의 port를 적어주었기 때문에, 이때 사용한 port와 똑같이 적어준다. 따라서 PORT(9000)을 할당하였다.
request로는 네이버 영화 랭킹 페이지의 URL인"/movie/sdb/rank/rmovie.nhn"를 GET 메소드로 호출하도록 정의하고, 이 경우에는 response는 statusCode 200과 함께 랭킹페이지 html을 넘겨주도록 지정하였다. (response 변수)
이때 response 변수는 아까 저장한 네이버 영화 랭킹 페이지의 html 파일이 리턴되어야 하므로, 아래와 같이 Stream 형태로 html 파일의 내용을 받아오는 로직을 짰다. (인자로는 리턴 받을 html 파일을 적어준다)
There are plenty of examples out there for crawling pages using Java's Jsoup, but it was hard to find code for testing the crawling. So I ended up writing this post about crawling tests that I implemented with the JavaBom study group — including spinning up an nginx server and crawling from it.
By the way, crawling from an nginx server doesn't really serve as a proper crawling test since it's an actual server. If the real server goes down, the crawling test can't run either, so you'd end up having to slap @Ignore on it anyway.
In my opinion, there are two key points when it comes to crawling tests.
1. Parsing Test
[ Purpose ] Verify that only the desired information was extracted from the page's HTML file through crawling.
[ Implementation ] Save the page you want to crawl as an .html file using ctrl+s, then store that HTML file in your Spring Boot project's resources. After that, load the file, run your crawling code against it, and verify that the parsed information matches what you expected. e.g.) Does the movie list extracted from the Naver Movie HTML have exactly 10 items? e.g.) Is the crawled movie title "The Martian"? etc. (my favorite movie lol)
2. Connect Test
[ Purpose ] Send a request to fetch the page via crawling, receive a response, and verify that your crawling code works correctly.
[ Implementation ] Create a mock server for testing. Just like how a movie page returns an HTML page when you send a URL request via GET method, we create a mock server to verify the result of Jsoup's connect. This mock server is configured so that when a specific URL is called via GET method — just like the Naver Movie page we want to crawl — it returns a specific HTML file.
[ Features ] 1. You can create and return a fixed response. 2. It can forward requests to another server. 3. It can execute callbacks. 4. You can inspect the requests.
For today's post, I'll be using the Naver Movie ranking page as the crawling target.
I used Jsoup for crawling and added Lombok to use the Builder pattern.
// NaverMovieCrawler.java
public class NaverMovieCrawler {
public static void main(String[] args) {
Document document = new NaverMovieCrawler().getCrawlingResult("https://movie.naver.com/movie/sdb/rank/rmovie.nhn");
TopMovieList topMovieList = new TopMovieList(document);
System.out.println(topMovieList.toString());
}
// url 을 보내면, 해당 페이지를 크롤링하여 Document 타입을 리턴한다.
public Document getCrawlingResult(String url){
try {
return Jsoup.connect(url)
.timeout(2000)
.get();
} catch (IOException e) {
throw new RuntimeException("crawling 실패");
}
}
}
It would be fine to build a Spring Boot API, but for now I just created a simple main method and ran it. The important part here is Jsoup.connect(url) — and this is essentially the reason we need mock server testing. Jsoup's connect() method basically sends a GET request, and you can chain various other methods provided by Jsoup to send more specific requests.
However, since we're just fetching the HTML file of the Naver Movie page, we can keep the connect() method simple with just a GET method + URL mapping.
@AllArgsConstructor
@Getter
public class TopMovieList {
private List<Movie> topMovies = new ArrayList<>();
public TopMovieList(Document document){
this.topMovies = document.select("table.list_ranking tr").stream()
.filter(x -> !x.select(".ac").isEmpty())
.map(Movie::of)
.collect(Collectors.toList());
}
@Override
public String toString() {
return topMovies.stream()
.map(Movie::toString)
.collect(Collectors.joining(", \n"));
}
}
As you can see in the main method, I created a first-class collection object called TopMovieList. It parses the Document object fetched via Jsoup (which returns the entire HTML) to build a List<Movie>. To create each individual Movie object, I split the table rows from the Naver Movie ranking table and delegate the parsing of each row's data to the Movie object's of() method.
// Movie.java
public class Movie {
private int rank;
private String title;
private String detailLink;
@Builder
private Movie(int rank, String title, String detailLink) {
this.rank = rank;
this.title = title;
this.detailLink = detailLink;
}
public static Movie of (Element element){
return Movie.builder()
.rank(Integer.parseInt(element.select(".ac img").attr("alt")))
.title(element.select(".title").text())
.detailLink(element.select(".title a").attr("href"))
.build();
}
@Override
public String toString() {
return "Movie{" +
"rank='" + rank + '\'' +
", title='" + title + '\'' +
", detailLink='" + detailLink + '\'' +
'}';
}
}
The Movie object takes an Element — a single row from the Naver Movie ranking table passed from the List<Movie> constructor — and binds the data. I used appropriate CSS selectors with the browser's developer tools to extract only the data I needed.
When you print the resulting List<Movie> object using toString() in the main method, you can see the output above, confirming that the crawling worked correctly.
2. Creating a MockServer
Add the modules needed for mock-server creation and testing. I used JUnit 5 for the tests.
First, go to the Naver Movie page you want to crawl and download the HTML. This is because the mock server — where we define our own request and response — needs to return the Naver Movie page's HTML file.
I saved it as ranking_naver_move.html. Then I placed the downloaded file inside the test > resources folder. Additionally, I ended up with one more HTML file because I implemented test code related to the parsing test I mentioned earlier.
When running tests related to the mock server, you need to start the mock server before each test and shut it down after each test. So we add those methods before and after the test code.
// NaverMovieCrawlerTest.java
public class NaverMovieCrawlerTest {
private static final int PORT = 9000;
private static ClientAndServer mockServer;
@BeforeEach
void setUp() {
mockServer = ClientAndServer.startClientAndServer(PORT);
System.out.println("mock server start");
}
@AfterEach
void tearDown() {
mockServer.stop();
System.out.println("mock server stop");
}
If you create an empty test with the @Test annotation and run it, you'll see a massive amount of logs like the ones below. Reading through some of the notable ones, you can see it automatically configures things like the Java version and cache size.
Also, the PORT value of 9000 passed as an argument specifies the port for the temporary mock server. After the sout line — that is, after the startClientAndServer method executes — you can see a log confirming that the mock server has been set up on port 9000.
Now let's do the detailed setup of the mock server. The mock server I'm building needs to perform the same role as the Naver Movie ranking page. So when the path "/movie/sdb/rank/rmovie.nhn" — which represents the Naver ranking page — is requested, it should return an HTML file. As you can see below, this can be configured using the .when() and .respond() methods.
The when part defines the request the mock server will receive, and the respond part defines the response to return when that request is matched.
First, specify localhost as the mock server host, and then the port to run the mock server on. Since we already specified the port when starting the mock server with the ClientAndServer.startClientAndServer() method, we use the same port here. So I assigned PORT(9000).
For the request, I defined it to call the Naver Movie ranking page URL "/movie/sdb/rank/rmovie.nhn" via GET method. In that case, the response is configured to return a status code of 200 along with the ranking page HTML. (the response variable)
The response variable needs to contain the HTML file of the Naver Movie ranking page that we saved earlier, so I wrote logic to read the HTML file contents as a Stream. (The argument specifies which HTML file to return.)
After finishing the mock server setup, if you run @Test with just the createNaverRankingPageServer() method, you'll see more logs — which, if you read through them, describe the mock server configuration you set up.
3. Parsing and Connect Tests Using MockServer
Now that the mock server setup is all done, let's write the crawling code to test. Call the createPathNoteServer() method we implemented earlier, which defines the mock server's request and response. (The argument represents the response value.)
Then, plug in the crawling code to fetch results from the mock server's address, port, and URL, and test the values.
[GET] http://localhost:9000/movie/sdb/rank/rmovie.nhn host localhost port 9000 path /movie/sdb/rank/rmovie.nhn
If you leave out "http" when writing the URL, it won't be recognized! Watch out for that.
// NaverMovieCrawlerTest.java
@BeforeEach
void setUp() {
mockServer = ClientAndServer.startClientAndServer(PORT);
System.out.println("mock server start");
}
@Test
public void naverMovieMockServerTest(){
createNaverRankingPageServer("ranking_naver_movie.html");
Document document = new NaverMovieCrawler().getCrawlingResult("http://localhost:9000/movie/sdb/rank/rmovie.nhn");
TopMovieList topMovieList = new TopMovieList(document);
assertThat(topMovieList.getTopMovies().size()).isEqualTo(50);
}
@AfterEach
void tearDown() {
mockServer.stop();
System.out.println("mock server stop");
}
After completing the code and running it, the test passed — confirming that the crawled data came through properly and parsing was completed successfully.
For the crawling parsing test, I used the Movie.test / TopMovieList.test files in the GitHub repo to verify that the crawling logic binds the data to objects exactly as I intended.
But the code is pretty straightforward, so I've omitted it here. Check out the GitHub repo :)
[Related Blog]
This post was written through the JavaBom study group, and we have a team blog :) The same content has been uploaded there as well.
java.lang.NoSuchMethodError:'com.google.api.client.http.HttpRequest com.google.api.client.http.HttpRequest.setResponseReturnRawInputStream(boolean)' at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.buildHttpRequest(AbstractGoogleClientRequest.java:434) ~[google-api-client-1.30.4.jar:1.30.4]
외부 라이브러리로 .jar 파일을 추가하면서 정말 골치를 썩었던 문제였다.
분명, 내가 build.gradle에 추가한 dependency는 가장 상위버전이고, NoSuchMethodError 와 관련한 글을 구글링 해보았을 때 하위 버전에서 발생하는 문제이니 상위버전으로 version을 업데이트 하라는 해결을 준다.
그런데 intellij 상으로 아래 메세지를 보면 1.30.4 버전으로 가장 상위버전으로 표시가 된다.
이때 의심해야할 것은 외부 라이브러리에서도 똑같은 dependency를 사용하는데 외부 라이브러리의 버전이 낮을 경우이다.
이 경우에 classPath에 혼동이 생겨서 일어난 에러로 보인다. 내가 build.gradle에 추가한대로 현재 프로젝트의 classpath를 따라야하는데, 외부 라이브러리인 jar 파일의 classpath와의 충돌 때문인 것 같다.
Solution : 나는 이경우에 외부 라이브러리인 jar 파일을 압축 해제후, 해당 모듈을 지우고 다시 jar로 압축하는 방식을 사용했다.
jar 파일 압축 / 압축 해제
jar 파일을 라이브러리로 만드려는 과정에서, jar 파일안에 사용되는 라이브러리 gson, slf4j 두개가 내가 사용하는 springboot dependency와 충돌이 나는 상황이 있었다.
Once you add this to build.gradle and run build gradle, you'll be able to load and use the Java files inside the external .jar library you added, as shown below.
3. Adding a dependency by turning a local directory into a library
This is also a way to add the libs/something_local.jar file.
java.lang.NoSuchMethodError:'com.google.api.client.http.HttpRequest com.google.api.client.http.HttpRequest.setResponseReturnRawInputStream(boolean)' at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.buildHttpRequest(AbstractGoogleClientRequest.java:434) ~[google-api-client-1.30.4.jar:1.30.4]
This was a really frustrating issue I ran into while adding an external .jar file as a library.
The dependency I added to build.gradle was clearly the latest version, and when I googled about NoSuchMethodError, the solutions all said it's an issue with older versions and to update to a newer version.
But looking at the message in IntelliJ below, it shows version 1.30.4, which is the latest version.
What you should suspect in this case is when the external library also uses the same dependency but with a lower version.
This error seems to be caused by confusion in the classPath. The current project should follow the classpath I specified in build.gradle, but it appears to conflict with the classpath of the external jar library.
Solution: In my case, I extracted the external jar file, removed the conflicting module, and then repackaged it as a jar.
Compressing / Extracting jar files
While trying to turn a jar file into a library, there was a situation where two libraries used inside the jar file — gson and slf4j — were conflicting with my Spring Boot dependencies.
So I initially tried to remove those two modules using the method below.
<!DOCTYPE html>
<html>
<head>
<script>
/* code will go here */
</script>
</head>
<body>
<h1>Image Styling with Web Components</h1>
<!-- elements will go here -->
</body>
</html>
1-2. 나의 첫번째 Custom Element 만들기
이미지를 이 페이지로 드래그앤 드롭을 하기 위한 코드를 작성해 보자. <codelab-dragdrop></codelab-dragdrop> 태그를 생성할 예정이며, 이 태그는 파일이 드롭되는 위치를 표시하는 곳을 나타낼 것이다.
이에 대한 로직은 Javascript를 이용하여 구현할 예정이다. 1. 새로운 element를 정의 2. element를 사용(인스턴스 화)
<codelab-dragdrop></codelab-dragdrop>
1-3. Element 정의
Custom Element는 HTMLElement라는 ES6의 클래스를 상속받은 것 이다.
ES6는 ECMAScript6의 줄임말으로 ECMAScript6는 자바스크립트 표준 단체인 ECMA가 제정하는 자바스크립트 표준이다.
자바스크립트는 프로토타입 기반(prototype-based) 객체지향 언어다. 프로토타입 기반 프로그래밍은 클래스가 필요없는(class-free) 객체지향 프로그래밍 스타일로 프로토타입 체인과 클로저 등으로 객체 지향 언어의 상속, 캡슐화(정보 은닉) 등의 개념을 구현할 수 있다.
ES6의 클래스는 기존 프로토타입 기반 객체지향 프로그래밍보다 클래스 기반 언어에 익숙한 프로그래머가 보다 빠르게 학습할 수 있는 단순명료한 새로운 문법을 제시하고 있다. 그렇다고 ES6의 클래스가 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새로운 객체지향 모델을 제공하는 것은 아니다. [출처] https://poiemaweb.com/es6-class
<script> 태그안에 Javascript 문법을 이용해서 나의 Custom Element를 정의한다.
/* code will go here */
class CodelabDragdrop extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
// we'll do stuff here later
console.info('Element connected!');
}
}
customElements.define('codelab-dragdrop', CodelabDragdrop);
connectedCallback() : document의 DOM에 정의한 custom element가 맨 처음 호출되었을 때 실행된다. customElements.defind(DOMString, class, {extends:'[tag-name]'}) - DOMString : 사용자가 element에 전달하려는 이름 (즉, 태그 네임). 이때 커스텀 엘리먼트의 이름들은 dash('-')가 포함된 이름을 사용해야하므로 주의해야한다! - class : element의 행위가 정의된 object이다. - extends (optional) : 상속받을 태그를 지정할 수 있다. 만약 { extends: 'p'} 이렇게 지정한다면, p 태그의 inline 성질을 갖고있는 객체가 되는 듯
개발자도구( option+command+i / F12 )를 이용해서 확인해보면 connection이 완료되었다는 메세지를 확인 할 수 있다.
2. Drag and Drop
2-1. Target 생성하기
<!-- elements will go here -->
<codelab-dragdrop>
<div style="width: 200px; height: 200px; background: red;">
</div>
</codelab-dragdrop>
페이지를 새로 고침하면 큰 빨간색 상자가 나타난다. 더 중요한 것은 페이지를 개발자 도구로 확인하면codelab-dragdrop 내부에 빨간색 사각형을 보유하고 있기 때문에 현재 200 x 200 픽셀의 크기를 갖고있음을 알 수 있다.
2-2. Handler 추가하기
codelab-dragdrop에 파일을 끌어다 놓을 수 있도록확장 해보자.
Web component가 가진 기능중에 하나는 캡슐화이다. 이를 수행할 수 있는 방법은 element defind 안에 코드를 추가하는 것이다. 이전에 작성한 ES6 클래스의 constructor element 자체에 리스너를 추가하여 메소드를 업데이트 할 것이다.
constructor() {
super(); // you always need super
this.addEventListener('dragover', (ev) => {
ev.preventDefault();
});
this.addEventListener('drop', (ev) => {
ev.preventDefault();
const file = ev.dataTransfer.files[0] || null;
file && this._gotFile(file);
});
}
super()는 항상 필요하다. Web Element와 관련한 클래스인 HTMLElement 클래스를 상속받고, 그 기능을 온전히 사용하기 위해서.
여기서 드래그 앤 드롭과 관련된 두 가지 이벤트를 처리합니다. 여기서 중요한 것은 drop 핸들러이다. 첫 번째 파일 (있는 경우)을 드래그하여 _getFile() 이라는 메소드를 호출하게 되어있다.
2-3. Event 방출하기
이 코드랩의 목표는 Image를 조작할 수 있는 것(조작기 - manipulator)을 만드는 것이다. 따라서 우리는 이미지를 만들고, 해당하는 조작기에 이미지를 넘겨주는 것을 목적으로 한다.
이 작업을 수행할 일반 인터페이스를 제공하는 가장 좋은 방법은 HTML 자체를 이용해서 우리가 사용 가능할 수 있게 하는 것이다. 이전 단계의 drop과 같이 event를 생성해 볼 것이다. drop된 파일에서 하나의 유효한 이미지를 생성하는 것 같이 이 이벤트는 우리의 목표에 맞게 구체적으로 설정되어있다. (추가하면 더 많은 작업을 할 수 있다 - 예 : 이미지의 크기를 먼저 조정한 다음 보낼 수 있다.)
이 코드에서 보는 것처럼 _getFile() 이라는 메소드를 작성한다. 이것은 File을 하나의 Image로 Load하며, custom한 Image로 방출한다.
_gotFile(file) {
const image = new Image();
const reader = new FileReader();
reader.onload = (event) => {
// when the reader is ready
image.src = event.target.result;
image.onload = () => {
// when the image is ready
const params = {
detail: image,
bubbles: true,
};
const ev = new CustomEvent('image', params);
this.dispatchEvent(ev);
};
};
reader.readAsDataURL(file);
}
reader가 준비가 된 경우, image객체의 src(경로) 속성에 drop 이벤트를 받은 타겟인 image의 결과값을 저장한다. 그렇게 해서 image가 준비가 잘 되었을 경우에 param이라는 변수를 정의하는데 이때 detail과 bubbles라는 속성을 지정하며. 우리가 image가 잘 들어왔는지 확인을 하기 위해서 CustomEvent라는 객체를 새로 생성한다. this.dispatchEvent(ev) : 적어도 하나의 이벤트 핸들러가 해당하는 이벤트를 처리하면서 이 메소드를 호출했다면 false를 반환하고, 그렇지 않으면 true를 반환한다.
마지막으로 이 element를 codelab-dragdrop element 안에 넣는다. 이때 이전에 있던 빨간색 상자는 제거한다. 이제 codelab-effect element가 target image를 제공하는 데 도움이 된다.
<!-- elements will go here -->
<codelab-dragdrop>
<codelab-effects></codelab-effects>
</codelab-dragdrop>
3-3. Putting It Together
이전 단계에 개발자 도구를 이용해서 event를 확인하려고 넣었던 스크립트를 기억하는가? 이 스크립트를 이용해서 이벤트를 연결해 보자!
<!-- elements will go here -->
<codelab-dragdrop id="dragdrop">
<codelab-effects id="effects"></codelab-effects>
</codelab-dragdrop>
<script>
dragdrop.addEventListener('image', (ev) => {
effects.image = ev.detail; // set the image that we got in dragdrop
});
</script>
이제 image 이벤트가 발생하면(즉 image를 drop 했다는 이벤트가 발생하면) codelab-effects 요소에 있는 image 속성을 설정하도록 코드가 장성되어있다. 이제 이렇게 제공한 이미지의 픽셀 데이터를 가져오는 것을 코드에 추가해 보자.
constructor() {
super();
this.root = this.attachShadow({mode: 'open'});
// Leave the root.innerHTML part alone
}
// Add this method
set image(image) {
const canvas = this.root.getElementById('canvas');
// resize image to something reasonable
canvas.width = Math.min(1024, Math.max(256, image.width));
canvas.height = (image.height * (canvas.width / image.width));
// clone buffer to get one of same size
const buf = canvas.cloneNode(true);
const ctx = buf.getContext('2d');
ctx.drawImage(image, 0, 0, buf.width, buf.height);
this.data = ctx.getImageData(0, 0, buf.width, buf.height).data;
console.info(this.data);
}
setter는 클래스 필드에 값을 할당할 때마다 클래스 필드의 값을 조작하는 행위가 필요할 때 사용한다. setter는 메소드 이름 앞에 set 키워드를 사용해 정의한다. 이때 메소드 이름은 클래스 필드 이름처럼 사용된다. 다시 말해 setter는 호출하는 것이 아니라 프로퍼티처럼 값을 할당하는 형식으로 사용하며 할당 시에 메소드가 호출된다.
이제 이 곳에 이미지를 Drag and Drop 하면 해당하는 이미지의 픽셀 데이터를 console에서 확인 할 수 있다.
3-4. 기본 스타일링
console.log()를 이용해서 데이터 숫자값만 보기 보다는 데이터를 가져와서 실제 캔버스로 그리는 작업을 해보자. set image 메서드 내부에서 만든 이미지 데이터를 보고 캔버스에 그린다. 이 코드랩은 캔버스 사용 및 이미지 데이터 작업에 관한 것이 아니라 웹 구성 요소를 시연하는데 도움이 되며 흥미로운 효과를 기대할 수 있다.
// Replace console.info with:
this.draw();
}
// And add this method
draw() {
const canvas = this.root.getElementById('canvas');
canvas.width = canvas.width; // clear canvas
const context = canvas.getContext('2d');
const amount = +this.root.getElementById('amount').value;
const size = amount * .8;
for (let y = amount; y < canvas.height; y += amount * 2) {
for (let x = amount; x < canvas.width; x += amount * 2) {
const index = ((y * canvas.width) + x) * 4;
const [r,g,b] = this.data.slice(index, index+3);
const color = `rgb(${r},${g},${b})`;
context.beginPath();
context.arc(x, y, size, 0, 360, false);
context.fillStyle = color;
context.fill();
}
}
}
페이지를 새로 고침하고 좋아하는 이미지를 페이지로 드래그하면 점묘 효과가 나타난다.
4.Saving Images
4-1. Click To Download
방금 만든 점묘화의 이미지를 공유하거나 사용하기 위해서는 마우스 오른쪽 버튼을 클릭해서 이미지를 다운로드 해야한다. 대신 canvas 아래에 링크를 추가하여 이미지를 자동으로 다운로드를 할 수 있도록 환경을 만들어 볼 예정이다.
그리고 이 link라는 id값을 받아서 download 할 수 있도록 이벤트 핸들러를 설정한다.
// And add this handler
const link = this.root.getElementById('link');
link.addEventListener('click', (ev) => {
link.href = this.root.getElementById('canvas').toDataURL();
link.download = 'pointify.png';
});
다운로드 완료!!
5. Control 추가
5-1. 응답하기
"AMOUNT" 슬라이더를 활용해보자! 이 것을 활용해서 우리가 그리는 점묘화의 점의 크기를 제어할 수 있다. 그러나 현재는 이미지 자체가 드롭될 때 한번만 발생하기 때문에 코드를 수정해보자
CodeLabEffects 클래스의 생성자 안에 리스너를 추가해 보자. 다시 리마인드 하면 이것은 this.root는 AMOUNT 슬라이더를 포함한 모든 Shadow DOM이 있는 곳이다. 이것은 Shadow DOM element에 의해 생긴 모든 변경에 응답하고 draw 메소드를 호출한다.
...
link.download = 'pointify.png';
});
//add these two new listeners
this.root.addEventListener('input', (ev) => this.draw());
this.root.addEventListener('change', (ev) => this.draw());
}
input이나 change와 관련한 이벤트가 들어왔을 경우에 draw()를 다시 실행한다.
draw() {
const canvas = this.root.getElementById('canvas');
canvas.width = canvas.width; // clear canvas
const context = canvas.getContext('2d');
const attenuation = this.root.getElementById('attenuation').checked;
const amount = +this.root.getElementById('amount').value;
const size = this.root.getElementById('size').value * amount;
const opacity = this.root.getElementById('opacity').value;
for (let y = amount; y < canvas.height; y += amount * 2) {
for (let x = amount; x < canvas.width; x += amount * 2) {
const index = ((y * canvas.width) + x) * 4;
const [r,g,b] = this.data.slice(index, index+3);
const color = `rgba(${r},${g},${b},${opacity})`;
const weight = 1 - ( this.data[ index ] / 255 );
const radius = (attenuation ? size * weight : size);
context.beginPath();
context.arc(x, y, radius, 0, 360, false);
context.fillStyle = color;
context.fill();
}
}
}
size : 원의 기본 반경을 제어한다 opacity : 원의 투명도를 제어한다. attenuation : 각 원의 어두운 정도에 따라 원의 크기를 조정한다.
5-3. 추가 기능
이미지 전체를 색조로 만드는 컬러 필터 다른 모양을 사용 정렬되지 않은 배치 등
이 component element 에는 많은 가능성이 있다!
Polymer와 같은 다양한 Web Component Element 라이브러리도 있다. 이 라이브러리에는 지금 렌더링 수준의 그림 하나하나 생각했던 것과 같은 Low Level의 Component Element보다 좀더 High Level 계층의 추상화된 라이브러리를 제공한다.
This is a summary I put together in Korean to prepare for a codelab seminar.
A new technology that lets you create reusable elements for HTML pages. They have custom user-defined names: you can bundle the tags you want and encapsulate them.
This is the process of creating a WebComponent using Custom Elements (codelab-dragdrop) and shadow DOM (codelab-effects). By combining these, we'll build a website that can manipulate images dragged onto the page.
How to declare Custom Elements
How to add listeners and handlers to a Component
How to create a Shadow Root to encapsulate Custom Design
How to compose multiple components to build a small application
<!DOCTYPE html>
<html>
<head>
<script>
/* code will go here */
</script>
</head>
<body>
<h1>Image Styling with Web Components</h1>
<!-- elements will go here -->
</body>
</html>
1-2. Creating My First Custom Element
Let's write the code for dragging and dropping an image onto this page. <codelab-dragdrop></codelab-dragdrop> — we're going to create this tag, and it will indicate the area where files can be dropped.
The logic for this will be implemented using Javascript. 1. Define a new element 2. Use the element (instantiate it)
<codelab-dragdrop></codelab-dragdrop>
1-3. Defining the Element
A Custom Element is an ES6 class that extends HTMLElement.
ES6 stands for ECMAScript 6. ECMAScript 6 is a JavaScript standard established by ECMA, the JavaScript standards body.
JavaScript is a prototype-based object-oriented language. Prototype-based programming is a class-free object-oriented programming style that can implement concepts like inheritance and encapsulation (information hiding) through prototype chains and closures.
ES6 classes provide a cleaner, simpler syntax that makes it easier for programmers familiar with class-based languages to learn quickly, compared to the traditional prototype-based object-oriented programming. That said, ES6 classes don't replace the existing prototype-based object-oriented model with a new one. [Source] https://poiemaweb.com/es6-class
We define our Custom Element using Javascript syntax inside the <script> tag.
/* code will go here */
class CodelabDragdrop extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
// we'll do stuff here later
console.info('Element connected!');
}
}
customElements.define('codelab-dragdrop', CodelabDragdrop);
connectedCallback(): This is called when the custom element is first inserted into the document's DOM. customElements.define(DOMString, class, {extends:'[tag-name]'}) - DOMString: The name you want to give the element (i.e., the tag name). Note that custom element names must include a dash ('-'), so keep that in mind! - class: An object that defines the element's behavior. - extends (optional): You can specify a tag to inherit from. For example, if you set { extends: 'p'}, the resulting object seems to take on the inline properties of a p tag.
If you check using the developer tools (option+command+i / F12), you can confirm the connection completed message.
2. Drag and Drop
2-1. Creating the Target
<!-- elements will go here -->
<codelab-dragdrop>
<div style="width: 200px; height: 200px; background: red;">
</div>
</codelab-dragdrop>
When you refresh the page, a big red box appears. More importantly, if you inspect the page with developer tools,you can see that the red square is contained inside codelab-dragdrop, so it currently has a size of 200 x 200 pixels.
2-2. Adding Handlers
Let's extend codelab-dragdrop so that files can be dragged and dropped onto it.
One of the features of Web Components is encapsulation. The way to achieve this is by adding code inside the element definition. We'll update the constructor of the ES6 class we wrote earlier by adding listeners to the element itself.
constructor() {
super(); // you always need super
this.addEventListener('dragover', (ev) => {
ev.preventDefault();
});
this.addEventListener('drop', (ev) => {
ev.preventDefault();
const file = ev.dataTransfer.files[0] || null;
file && this._gotFile(file);
});
}
super() is always required — to inherit from HTMLElement, the class related to Web Elements, and to fully use its features.
Here we handle two events related to drag and drop. The important one here is the drop handler. It takes the first file (if there is one) from the drag and calls a method called _getFile().
2-3. Emitting Events
The goal of this codelab is to build something that can manipulate images (a manipulator). So our objective is to create an image and pass it to the corresponding manipulator.
The best way to provide a general interface for this is to make it available to us through HTML itself. Just like the drop event from the previous step, we're going to create an event. Like generating a single valid image from a dropped file, this event is specifically tailored to our goal. (You can do more if you add to it — for example, you could resize the image first before sending it.)
As you can see in this code, we write a method called _getFile(). It loads a File as an Image and emits it as a custom Image event.
_gotFile(file) {
const image = new Image();
const reader = new FileReader();
reader.onload = (event) => {
// when the reader is ready
image.src = event.target.result;
image.onload = () => {
// when the image is ready
const params = {
detail: image,
bubbles: true,
};
const ev = new CustomEvent('image', params);
this.dispatchEvent(ev);
};
};
reader.readAsDataURL(file);
}
When the reader is ready, the result of the drop event target image is stored in the image object's src (path) property. Once the image is successfully loaded, we define a variable called params with detail and bubbles properties. To verify that the image was received correctly, we create a new CustomEvent object. this.dispatchEvent(ev): Returns false if at least one event handler that handled the event called this method, and true otherwise.
If an image is dropped onto the designated area, it shows the image information in the console. This is where the CustomEvent we set up earlier kicks in.
3. Connecting Element
3-1. Creating My Second Custom Element
Let's create a second Custom Element called codelab-effects. This element will render the image and can apply interesting visual effects to it.
Shadow DOM allows you to add custom HTML that isn't actually part of the page's main DOM. The method used to enable Shadow DOM is attachShadow().
Depending on the mode of attachShadow(), you can control whether the HTML code inside the element is visible in the developer tools or not.
It's an element that isn't visible in the actual developer tools, and it can typically be accessed using document.querySelector() or getElementById().
Not every Custom Element needs a Shadow Root. In fact, the <codelab-dragdrop> element performs complex logic for manipulating dropped files without creating a new one. But it's a really powerful API.
You can define a template by adding a few lines of code for the HTML inside the Shadow DOM.
Finally, we place this element inside the codelab-dragdrop element. Remove the red box from before. Now the codelab-effects element helps provide the target image.
<!-- elements will go here -->
<codelab-dragdrop>
<codelab-effects></codelab-effects>
</codelab-dragdrop>
3-3. Putting It Together
Remember the script we pasted in the developer tools to check the event in the previous step? Let's use this script to connect the events!
<!-- elements will go here -->
<codelab-dragdrop id="dragdrop">
<codelab-effects id="effects"></codelab-effects>
</codelab-dragdrop>
<script>
dragdrop.addEventListener('image', (ev) => {
effects.image = ev.detail; // set the image that we got in dragdrop
});
</script>
Now the code is set up so that when an image event fires (i.e., when an image is dropped), it sets the image property on the codelab-effects element. Let's now add code to grab the pixel data from the provided image.
constructor() {
super();
this.root = this.attachShadow({mode: 'open'});
// Leave the root.innerHTML part alone
}
// Add this method
set image(image) {
const canvas = this.root.getElementById('canvas');
// resize image to something reasonable
canvas.width = Math.min(1024, Math.max(256, image.width));
canvas.height = (image.height * (canvas.width / image.width));
// clone buffer to get one of same size
const buf = canvas.cloneNode(true);
const ctx = buf.getContext('2d');
ctx.drawImage(image, 0, 0, buf.width, buf.height);
this.data = ctx.getImageData(0, 0, buf.width, buf.height).data;
console.info(this.data);
}
A setter is used when you need to manipulate the value of a class field every time a value is assigned to it. A setter is defined by placing the set keyword before the method name. The method name is then used as if it were a class field name. In other words, a setter isn't called like a regular method — it's used in the format of assigning a value like a property, and the method is invoked upon assignment.
Now if you drag and drop an image here, you can see the pixel data of that image in the console.
3-4. Basic Styling
Rather than just looking at data numbers via console.log(), let's actually grab the data and draw it on the canvas. We take the image data created inside the set image method and draw it on the canvas. This codelab isn't about working with canvas or image data — it's about demonstrating Web Components, and you can look forward to some interesting effects.
// Replace console.info with:
this.draw();
}
// And add this method
draw() {
const canvas = this.root.getElementById('canvas');
canvas.width = canvas.width; // clear canvas
const context = canvas.getContext('2d');
const amount = +this.root.getElementById('amount').value;
const size = amount * .8;
for (let y = amount; y < canvas.height; y += amount * 2) {
for (let x = amount; x < canvas.width; x += amount * 2) {
const index = ((y * canvas.width) + x) * 4;
const [r,g,b] = this.data.slice(index, index+3);
const color = `rgb(${r},${g},${b})`;
context.beginPath();
context.arc(x, y, size, 0, 360, false);
context.fillStyle = color;
context.fill();
}
}
}
Refresh the page and drag your favorite image onto it — you'll see a pointillism effect appear.
4.Saving Images
4-1. Click To Download
To share or use the pointillism image we just created, you'd have to right-click and download the image. Instead, we're going to add a link below the canvas so that the image can be downloaded automatically.
Then we set up an event handler using the link id to enable the download.
// And add this handler
const link = this.root.getElementById('link');
link.addEventListener('click', (ev) => {
link.href = this.root.getElementById('canvas').toDataURL();
link.download = 'pointify.png';
});
Download complete!!
5. Adding Controls
5-1. Responding to Input
Let's make use of the "AMOUNT" slider! We can use it to control the size of the dots in our pointillism image. But right now, the drawing only happens once when the image is dropped, so let's fix the code.
Let's add a listener inside the constructor of the CodelabEffects class. As a reminder, this.root is where all the Shadow DOM lives, including the AMOUNT slider. This will respond to any changes made by Shadow DOM elements and call the draw method.
...
link.download = 'pointify.png';
});
//add these two new listeners
this.root.addEventListener('input', (ev) => this.draw());
this.root.addEventListener('change', (ev) => this.draw());
}
When an input or change event comes in, it re-executes draw().
The rendering code, draw(), also needs a slight update to accommodate the controls.
draw() {
const canvas = this.root.getElementById('canvas');
canvas.width = canvas.width; // clear canvas
const context = canvas.getContext('2d');
const attenuation = this.root.getElementById('attenuation').checked;
const amount = +this.root.getElementById('amount').value;
const size = this.root.getElementById('size').value * amount;
const opacity = this.root.getElementById('opacity').value;
for (let y = amount; y < canvas.height; y += amount * 2) {
for (let x = amount; x < canvas.width; x += amount * 2) {
const index = ((y * canvas.width) + x) * 4;
const [r,g,b] = this.data.slice(index, index+3);
const color = `rgba(${r},${g},${b},${opacity})`;
const weight = 1 - ( this.data[ index ] / 255 );
const radius = (attenuation ? size * weight : size);
context.beginPath();
context.arc(x, y, radius, 0, 360, false);
context.fillStyle = color;
context.fill();
}
}
}
size: Controls the base radius of the circles. opacity: Controls the transparency of the circles. attenuation: Adjusts the size of each circle based on how dark it is.
5-3. Additional Features
A color filter that tints the entire image Using different shapes Randomized placement, etc.
There are so many possibilities with this component element!
There are also various Web Component element libraries like Polymer. These libraries provide higher-level abstractions rather than the low-level component elements we were working with here, where we had to think about each individual rendering detail.
test {
useJUnitPlatform()
}
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompile("org.assertj:assertj-core:3.11.1")
}
1. assertAll
User user = new User();
assertAll(
() -> asssertThat(user.getId()).isEqualTo(1L), //1
() -> asssertThat(user.getName()).isEqualTo("jyami"), //2
() -> asssertThat(user.getEmail()).isEqualTo("mor222293@gmail.com") //3
);
이전의 Junit4에서는 assertEquals을 검증할 때, 위에부터 하나씩 실행하는데, 위에서 실패하면 아래에있는 assertEquals를 실행하지 않는다.즉, 주석의 1번의 assertEquals에서 실패했으면 2, 3번의 assertEquals는 실행하지 않는다. 그러나 assertAll을 사용하면, 1번에서 실패했어도 2번 3번도 실행한다.
2. @DisplayName
@DisplayName("유저 테스트")
public class UserTest {
@DisplayName("유저의 이름을 테스트 해보자!")
@Test
void someTest() {
User user = new User();
assertThat(user.getName()).isEqualTo("jyami");
}
이렇게 할 경우에는, DisplayName을 이용해서 test의 목적을 명확히 명시할 수 있다.
contains는 순서와 상관 없이 실제 그룹이 주어진 값들을 포함하고 있는지를 테스트한다. 그래서 위 두 줄의 테스트는 모두 통과한다.
2. containsExactly()
@Test
void containsExactlyTest() {
List<Integer> integers = Arrays.asList(1, 2, 3);
assertThat(integers).containsExactly(1, 2, 3); //테스트 통과
assertThat(integers).containsExactly(2, 1, 3); //테스트 통과 X
assertThat(integers).containsExactly(1, 2); //테스트 통과 X
}
contains는 순서까지 고려해서 실제 그룹이 주어진 값들을 포함하고 있는지를 테스트한다 그래서 첫번째 줄의 테스트는 통과하지만, 두번째 줄의 테스트는 통과하지 못한다.
이때 주의할 점은 원소 하나라도 빠지면 테스트를 통과하지 못한다. 정말로 정확하게 일치하는 list여야 하는 것!
업데이트 예정인 게시글 입니다.
0. build.gradle
test {
useJUnitPlatform()
}
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompile("org.assertj:assertj-core:3.11.1")
}
1. assertAll
User user = new User();
assertAll(
() -> asssertThat(user.getId()).isEqualTo(1L), //1
() -> asssertThat(user.getName()).isEqualTo("jyami"), //2
() -> asssertThat(user.getEmail()).isEqualTo("mor222293@gmail.com") //3
);
In the previous JUnit4, when verifying with assertEquals, it executes them one by one from the top, and if one fails, it doesn't execute the ones below it. In other words, if assertEquals #1 in the comments fails, assertEquals #2 and #3 won't run. However, if you use assertAll, even if #1 fails, #2 and #3 still get executed.
2. @DisplayName
@DisplayName("유저 테스트")
public class UserTest {
@DisplayName("유저의 이름을 테스트 해보자!")
@Test
void someTest() {
User user = new User();
assertThat(user.getName()).isEqualTo("jyami");
}
By doing this, you can use DisplayName to clearly state the purpose of each test.
contains tests whether the actual group contains the given values regardless of order. So both lines of tests above will pass.
2. containsExactly()
@Test
void containsExactlyTest() {
List<Integer> integers = Arrays.asList(1, 2, 3);
assertThat(integers).containsExactly(1, 2, 3); //테스트 통과
assertThat(integers).containsExactly(2, 1, 3); //테스트 통과 X
assertThat(integers).containsExactly(1, 2); //테스트 통과 X
}
containsExactly tests whether the actual group contains the given values while also considering the order. So the first line's test passes, but the second line's test does not.
One thing to note here is that if even a single element is missing, the test will fail. The list really has to match exactly!
This is a post that will be updated in the future.
Cloud Dataproc: Managed Hadoop MapReduce, Spark, Pig, Hive service
Cloud Dataflow: Stream and batch processing, unified pipeline integration and simplification
BigQuery: Analytics database, data streaming at 100,000 rows per second
Cloud Pub/Sub: Scalable and flexible enterprise messaging
Cloud Datalab: Interactive data exploration
1-2. Cloud Dataproc
Features
Managed Hadoop
A fast and easy way to run GCP Hadoop and Spark/Hive/Pig as a managed service
Average cluster creation time under 90 seconds
Scale clusters up and down even while jobs are running
Why you should use it
Easily migrate on-premises Hadoop workloads to the cloud.
Quickly analyze data such as logs stored in Cloud Storage, create clusters in under 90 seconds on average, and delete them immediately.
Perform data mining and analysis quickly using Spark/Spark SQL.
Run classification algorithms using the Spark machine learning library (MLlib).
1-3. Cloud Dataflow
Features
Managed data pipelines
Processes data using Compute Engine instances
Automatic cluster size adjustment
Automated scaling, no instance provisioning required
Write code once for both batch and stream processing
Transform-based programming model
Data moves from sources through transformations in a Dataflow pipeline
Why you should use it
Move, filter, enrich, and shape data with ETL (Extract/Transform/Load) pipelines
Data analytics: Batch computation or continuous computation using streaming
Orchestration: Build pipelines that coordinate multiple services, including external services
Integrates with GCP services such as Cloud Storage, Cloud Pub/Sub, BigQuery, and Bigtable
Open source Java and Python SDKs
1-4. BigQuery
Fully managed data warehouse
Provides near real-time interactive analysis on massive datasets (hundreds of TB)
Queries using SQL syntax (SQL 2011)
No cluster maintenance required
Runs on Google's high-performance infrastructure
Compute and storage separated by a terabit-class network
Pay only for storage used and processing performed
Automatic discounts for long-term data storage
1-5. Cloud Pub/Sub
Features
Scalable and reliable messaging
Supports many-to-many asynchronous messaging: Create push/pull subscriptions to topics from application components
Includes offline consumer support
Leverages proven Google technology
Why you should use it
A building block for data ingestion in Dataflow, Internet of Things (IoT), and marketing analytics
Foundation for Dataflow streaming
Push notifications for cloud-based applications
Connect multiple applications within Google Cloud Platform (push/pull between Compute Engine and App Engine)
1-6. Cloud Datalab
Features
Provides interactive data exploration
An interactive tool for large-scale data exploration, transformation, analysis, and visualization
Integrated, open source: Built on Jupyter (IPython)
Why you should use it
Create and manage code, documentation, results, and visualizations in an intuitive notebook format Use Google Charts or matplotlib for easy visualization
Analyze data from BigQuery, Compute Engine, and Cloud Storage using Python, SQL, and JavaScript
Easily deploy models to BigQuery
2. Google Cloud AI Platform
2-1. Cloud AI Platform
An open source tool for building and running neural network models
Broad platform support: CPU or GPU, mobile, server, cloud
Fully managed machine learning service
Familiar notebook-based developer environment
Optimized for Google infrastructure, integrated with BigQuery and Cloud Storage
Pre-trained machine learning models built by Google
Speech: Streams results in real time and understands 80 languages
Vision: Identifies objects, landmarks, text, and content
Translation: Language detection and translation
Natural Language: Meaning and structure of text
Structured data: Classification and regression / Recommendations / Anomaly detection
Unstructured data: Image and video analysis / Text analysis
2-2. Cloud Vision API
Analyze images with a simple REST API
Logo detection, label recognition, and more
Features provided by Cloud Vision API
Extract useful information from images
Detect inappropriate content
Sentiment analysis
Text extraction
2-3. Cloud Speech API
Recognizes over 80 languages and variants
Can return text in real time
Provides high accuracy even in noisy environments
Accessible from any device
Powered by Google machine learning
2-4. Cloud Natural Language API
Uses machine learning models to understand the structure and meaning of text
Extracts information about topics mentioned in text documents, news articles, and blog posts.
Analyzes uploaded text on request or integrates with Cloud Storage.
2-5. Cloud Translation API
Translates arbitrary strings between numerous language pairs
Programmatically detects the language of a document
A service that provides the infrastructure level. The customer directly manages the OS and applications.
PaaS : Platform as a Service - heroku
A cloud service that provides developers with the capabilities needed to develop and serve applications. Users only manage applications and data.
2. Introduction to Containers
IaaS : Virtualizes hardware and allows resource sharing.
However, flexibility comes at the cost of boot time (minutes) and resources (GB).
App Engine
Provides access to programming services
A platform that rapidly scales apps independently based on workload and infrastructure as app demand grows
2-1. Containers
What containers provide
Offers the scalability of both IaaS and PaaS.
An abstraction layer over hardware and OS
An invisible box that provides configurable access to file systems, RAM, and networking divided into isolated partitions
Fast startup
Container capabilities
Configurable, independent, and highly portable.
Define your own hardware, OS, and software stack configuration
By treating the OS and hardware as a black box, there's no need to change or rebuild anything when migrating from development to staging to production, or from a laptop to the cloud.
A container is app + libraries: the OS/hardware implements the container interface
2-2. Clusters
Cluster capabilities
Allows deploying containers to server groups with a shared host configuration.
Connects multiple containers using network connections
Write modular code
Easy deployment
Achieve maximum efficiency and savings through independent scaling of containers and hosts
3. Kubernetes and Kubernetes Engine
3-1. Kubernetes
Easily orchestrates many containers across multiple hosts.
Build and run an app as a container
Docker : Bundles the app, dependencies, and system settings together
Other tools like Google Cloud Build can also be used. Code example: a Python Flask app that displays hello world
[app.py]
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "helloworld!"
if __name__ == "__main__":
app.run(host='0.0.0.0');
Bring the app to Kubernetes - Specify 4 things using a Docker file
The requirements.txt file for Flask dependencies
The OS image and version for Python
How to install Python
How to run the app
[requirement.txt]
Flask==0.12
uwsgi==2.0.15
FROM ubuntu:18.10
RUN apt-get update -y && \
apt-get install -y python3-pip python3-dev
COPY requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip3 install -r requirements.txt
COPY ./app
ENDPOINT ["python3", "app.py"]
Build the container into an image and run it
Use docker build to build the container and save it as a locally runnable image
Upload images to a registry service (such as Google Container Registry) for sharing
Deploy containers to a collection of nodes called a cluster using the Kubernetes API
The master runs the control plane
Nodes run the containers
Nodes are VMs (used as GCE instances in GKE)
You describe the app, and Kubernetes figures out how to implement it
Bootstrapping Kubernetes Engine
In a GKE cluster, you can specify the following > Machine type > Number of nodes > Network settings, etc.
$> gcloud container clusters create k1
Use a wrapper called a Pod when deploying containers to nodes
Run a container in a Pod using Kubectl run
Kubectl is a command-line client for the Kubernetes API
This command starts a deployment with a container running in a Pod
In this case, the container is an image of an NGINX server
$> kubectl run nginx --image=nginx:1.15.7
Deployment
Manages a set of replica Pods for an app or workload, ensuring the desired number of Pods are running and remain healthy
$> kubectl get pods
Pods are only accessible within the cluster by default and have ephemeral IPs
Run Kubectl expose to attach a load balancer to the deployment so it's publicly accessible at a static IP
Kubernetes creates a service using the Pod's static IP, and the controller displays a message saying 'I need to attach an external load balancer with a public IP address'
Clients reaching this IP are routed to the Pods behind the service
For example, if you create two sets of Pods named frontend and backend and place them behind their own services, changes in the backend Pods won't be noticed by the frontend Pods. They simply reference the backend service.
Run kubectl get services to get the public IP of the service
$> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.0.65.118 104.198.149.140 80/TCP 5m
Run kubectl scale to scale the deployment
$> kubectl scale nginx ==replicas=3
Use various parameters to enable autoscaling, or place autoscaling behind programming logic for intelligent management
Run kubectl apply -f to declaratively apply changes
$> kubectl apply -f nginx-deployment.yaml
Run kubectl get replicasets to check the update status
$> kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-2035384211 5 3 3 2s
Run kubectl get pods to verify the Pods are coming online
$> kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-203584211-7ci7o 1/1 Running 0 18s
nginx-203584211-he3h3 1/1 Running 0 18s
nginx-203584211-qqcnn 1/1 Running 0 18s
nginx-203584211-abbcc 1/1 Running 0 18s
nginx-203584211-knlen 1/1 Running 0 18s
Run kubectl get deployments to describe the deployment and verify the correct number of replicas are running
$> kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 5 5 5 5 18s
Build the container and run the image
$> kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.0.65.118 104.198.149.140 80/TCP 5m
4. Google App Engine
A PaaS for building scalable applications
App Engine makes deployment, maintenance, and scaling easy, so you can focus solely on innovation
Particularly well-suited for building scalable web applications and mobile backends
5. Google App Engine Standard Environment
Easy application deployment
Automatic workload scaling in response to demand
Cost-effective
Free daily quota / usage-based pricing
SDKs for development, testing, and deployment
Supports specific versions of Java, Python, PHP, and Go
Applications must comply with sandbox constraints
No writing to the local file system
A 60-second timeout applies to all requests
Third-party software installation is restricted
Example web application
6. Google App Engine Flexible Environment
Build and deploy containerized apps with a single click
No sandbox constraints
Access to App Engine resources
Standard runtimes: Python, Java, Go, Node.js
Custom runtime support: Any language that supports HTTP requests
Package the runtime as a Dockerfile
App Engine Environment Comparison
7. Google Cloud Endpoints and Apigee Edge
Application programming interfaces hide implementation details and enforce contracts [diagram]
7-1. Cloud Endpoints
Supports API creation and maintenance
Distributed API management through the API console
Expose APIs using RESTful interfaces
Access control and call validation using JSON Web Tokens and Google API keys -> Verify web and mobile user identity through Auth0 and Firebase Authentication
Client library generation
Supported platforms [diagram]
7-2. Apigee Edge
Supports API security and monetization
A platform where customers and partners can use APIs
Provides analytics, monetization, and developer portal
8. Development, Deployment, and Monitoring in the Cloud
8-1. Cloud Source Repositories
Fully-featured Git repositories hosted on Google Cloud Platform
Supports collaborative development of cloud apps
Provides integration with Stackdriver Debugger
8-2. Cloud Functions
Create single-purpose functions that respond to events without a server or runtime Event examples: A new instance is created. A file is added to Cloud Storage.
Written in Javascript, runs in a managed Node.js environment on Google Cloud Platform
8-3. Deployment Manager
Infrastructure management service
Create .yaml templates that describe your environment and use Deployment Manager to create resources
전역 Cloud Load Balancing으로 전 세계에 단일 프런트 엔드로 제공되는 애플리케이션
사용자가 단일 글로벌 Anycast IP 주소를 사용
Google 백본을 통해 가장 가까운 접속 지점에서 사용자에게로 트래픽 전달
부하를 기준으로 백엔드 선택
정상 백엔드만 트래픽 수신
가동 준비가 필요 없음
Cloud DNS
관리형 영역을 생성한 후 DNS 레코드를 추가, 편집, 삭제
RESTful API 또는 명령줄 인터페이스를 사용해 영역과 레코드를 프로그래매틱 방식으로 관리
Cloud CDN (Content Delivery Network)
전세계에 분산된 Google의 에지 캐시를 사용하여 콘텐츠를 사용자에게 가깝게 캐싱
다른 CDN의 사용을 원하는 경우 CDN Interconnect 사용
다양한 상호 연결 옵션을 제공
VPN : VPN 터널을 통한 Gbps 단위의 연결 확보
다이렉트 피어링 : 하이브리드 클라우드 워크로드를 위한 사용자와 Google간 비공개 연결
Dedicated Interconnect : Google 접속 지점에서 Google Cloud로 전달되는 프라이빗 클라우드 트래픽을 위한 N X 10G 전송 회로 연결
이동통신사 피어링 : 서비스 제공업체의 대규모 파트너 네트워크를 통한 연결
Partner Interconnect : 지원되는 서비스 제공업체를 통한 온프레미스 네트워크 및 VPC 네트워크 간 연결
부하 분산 옵션 제품군을 제공하는 Google VPC
글로벌 HTTP(S)
글로벌 SSL 프록시
글로벌 TCP 프록시
리전
리전 내부
부하를 기준으로한 Layer 7 부하 분산
부하를 기준으로 한 HTTPS외 SSL 트래픽의 Layer 4 부하 분산
SSL외 TCP 트래픽의 Layer 4 부하 분산
모든 트래픽(TCP, UDP)의 부하 분산
VPC내 트래픽의 부하 분산
각종 URL을 다양한 백엔드에 라우팅 할 수 있음
특정 포트 번호에서 지원됨
특정 포트번호에서 지원됨
모든 포트번호에서 지원됨
다중 계층 애플리케이션의 내부 계층에 사용
4. GCP - 스토리지
5. Cloud Storage
Cloud Storage의 특징
BLOB (binary large-object) 스토리지
고성능 인터넷 규모 : 단순한 관리
용량을 관리할 필요가 없음
저장 데이터 암호화
기본적으로 Google에서 엔드포인트로 전송 중인 데이터 암호화
온라인 및 오프라인 가져오기 서비스 사용 가능
Cloud Storage 파일은 버킷으로 구성
Cloud Storage 등급 선택하기
Cloud Stoarge로 데이터를 가져오는 3가지 방법
온라인 전송 : 명령줄 도구 또는 드래그인 드롭을 사용한 자체 관리형 사본
Storage Transfor Service : 일정에 따른 관리형 일괄 전송
Transfer Appliance : 데이터를 안전하게 전송하는, 랙 장착 가능한 어플라이언스
다른 GCP 서비스와 함께 사용이 가능하다.
6. Cloud Bigtable
Cloud Bigtable의 특징
관리형 NoSQL
테라바이트 규모의 애플리케이션을 위한 완전 관리형 NoSQL, 광폭 열 데이터베이스 서비스
HBase API로 액세스
빅데이터, Hadoop 에코시스템과 기본적으로 호환
복제 스토리지
가동 여부와 관계없이 상시 데이터 암호화
역할 기반 ACL
google Analytics, Gmail등의 주요 애플리케이션 실행
Bigtable 액세스 패턴
Application API 관리형 VM, HBase REST 서버, HBase 클라이언트를 사용하는 자바 서버와 같은 데이터 서비스 계층을 통해 Cloud Bigtable에서 데이터를 읽고 쓸 수 있다. 일반적으로 애플리케이션, 대시보드, 데이터 서비스에 데이터를 제공한다.
스트리밍 Cloud Dataflow Streaming, Spark Streaming, Storm과 같은 다양한 인기 스트림 처리 프레임워크를 통해 데이터를 스트리밍(이벤트별로 쓰기) 할 수 있다.
일괄 처리 Hadoop 맵 리듀스, Dataflow, Spark와 같은 일괄 프로세스를 통해 Cloud Bigtable에서 데이터를 읽고 쓸 수 있다. 요약 데이터 또는 새로 계산괸 데이터가 Cloud Bigtable 또는 다운스트림 데이터베이스에 다시 기록되는 경우가 많다.
7. Cloud SQL 및 Cloud Spanner
7-1. Cloud SQL
Cloud SQL의 특징
관리형 RDBMS
MySQL및 PostgreSQL 데이터베이스를 서비스로 제공
자동 복제
관리형 백업
수직 확장 (읽기 및 쓰기)
수평 확장 (읽기)
Google 보안
다른 GCP 서비스와 함께 사용 가능
Cloud SQL + App Engine 표준 드라이버를 통해 Cloud SQL과 App Engine을 함께 사용할 수 있다. App Engine 애플리케이션을 따르도록 Cloud SQL인스턴스를 구성할 수 있다.
Cloud SQL + Compute Engine Compute Engine 인스턴스에서 외부 IP 주소를 사용하여 Cloud SQL 인스턴스에 액세스 하도록 승인할 수 있다. Cloud SQL 인스턴스를 선호 영역에 구성할 수 있다.
Cloud SQL + 외부 서비스 Cloud SQL을 외부 애플리케이션 및 클라이언트와 함께 사용할 수 있다. 표준 도구를 사용하여 데이터베이스를 관리할 수 있다. 외부 읽기 복제본을 구성할 수 있다.
7-2. Cloud Spanner
수평으로 확장 가능한 RDBMS
자동 복제
강력한 전역 일관성
가용성이 높은 관리형 인스턴스
SQL (ANSI 2011, 확장 포함)
8. Cloud Datastore
수평으로 확장 가능한 NoSQL DB
애플리케이션 백엔드용으로 설계된 NoSQL
완전 관리형 : 분산 아키텍처를 사용해 확장을 자동 관리
내장형 중복 기능
ACID 트랜잭션 지원
스키마 없는 액세스 : 기본 데이터 구조를 고려할 필요 없음
로컬 개발 도구
무료 일일 할당량 포함
RESTful 인터페이스를 통해 어디서나 액세스
9. 스토리지 옵션 비교
9-1. 기술 세부 정보
Cloud Datastore
Cloud Storage
Cloud Bigtable
Cloud SQL
Cloud Spanner
BigQuery
유형
NoSQL 문서
Blobstore
NoSQL 광폭 열
OLTP용 관계형 SQL
OLTP용 관계형 SQL
OLAP용 관계형 SQL
트랜잭션
O
X
단일 행
O
O
X
복잡한 쿼리
X
X
X
O
O
O
용량
TB+
PB+
PB+
10TB
PB
PB+
단위 크기
1MB/항목
-10MB/셀 -100MB/행
5TB/객체
DB엔진 결정
10.240MiB/행
10MB/행
9-2. 사용 사례
Cloud Datastore
Cloud Storage
Cloud Bigtable
Cloud SQL
Cloud Spanner
BigQuery
유형
NoSQL 문서
Blobstore
NoSQL 광폭 열
OLTP용 관계형 SQL
OLTP용 관계형 SQL
OLAP용 관계형 SQL
권장용도
반구조화 애플리케이션 데이터, 내구성 높은 키-값 데이터
'플랫' 데이터, 집중적인 읽기 /쓰기, 이벤트, 분석 데이터
구조화 및 비구조화 바이너리 또는 객체 데이터
웹 프레임워크, 기존 애플리케이션
대규모 데이터 베이스 애플리케이션 (약 2TB이상)
대화형 쿼리, 오프라인 분석
사용사례
입문용, App Engine 애플리케이션
AdTech, 금융, IoT 데이터
이미지, 대용량 미디어 파일, 백업
사용자 인증 정보, 고객 주문
높은 I/O, 전역적인 일관성이 필요한 모든 경우
데이터 웨어하우스
단위크기
1MB/항목
-10MB/셀 -100MB/행
5TB/객체
DB엔진 결정
10.240MiB/행
10MB/행
This is a summary of the handout distributed at the Google Cloud OnBoard event held at Sejong University on November 26, 2019.
You can provision Cloud Platform resources and connect or isolate them from each other.
VPC networks are configured at the global level, while subnets are configured at the regional level
2. Compute Engine
Provides managed virtual machines
High-performance CPUs, large memory, standard and shared-core machine types
Persistent disks (similar to a hard drive)
Standard, SSD, Local SSD
Snapshots
Resize disks with no downtime
Instance metadata and startup scripts
Customer-friendly pricing
Per-second billing, sustained use discounts, committed use discounts
Preemptible instances
Higher storage throughput at no additional cost
Custom machine types: pay only for the hardware you need
Scale out or scale up
Use large VMs for memory and compute-intensive applications
Use autoscaling for elastic, scalable applications
3. Key VPC Features
Control the topology of VPC networks
Use route tables to forward traffic within the network as well as between subnets
Use firewalls to control what network traffic is allowed
Use Shared VPC to share a network or individual subnets with other GCP projects
Use VPC Peering to interconnect networks across GCP projects
Global Cloud Load Balancing provides applications served by a single frontend worldwide
Users use a single global Anycast IP address
Traffic is routed to the user from the nearest point of presence via the Google backbone
Backends are selected based on load
Only healthy backends receive traffic
No pre-warming required
Cloud DNS
Create managed zones, then add, edit, and delete DNS records
Programmatically manage zones and records using a RESTful API or command-line interface
Cloud CDN (Content Delivery Network)
Cache content close to users using Google's globally distributed edge caches
Use CDN Interconnect if you prefer to use another CDN
Offers a variety of interconnect options
VPN: Secure Gbps-level connectivity through VPN tunnels
Direct Peering: Private connection between you and Google for hybrid cloud workloads
Dedicated Interconnect: N x 10G transport circuits for private cloud traffic from a Google point of presence to Google Cloud
Carrier Peering: Connectivity through a service provider's extensive partner network
Partner Interconnect: Connectivity between on-premises networks and VPC networks through a supported service provider
Google VPC provides a suite of load balancing options
Global HTTP(S)
Global SSL Proxy
Global TCP Proxy
Regional
Regional Internal
Layer 7 load balancing based on load
Layer 4 load balancing for non-HTTPS SSL traffic based on load
Layer 4 load balancing for non-SSL TCP traffic
Load balancing for all traffic (TCP, UDP)
Load balancing for traffic within a VPC
Can route various URLs to different backends
Supported on specific port numbers
Supported on specific port numbers
Supported on any port number
Used for internal tiers of multi-tier applications
4. GCP - Storage
5. Cloud Storage
Features of Cloud Storage
BLOB (binary large-object) storage
High performance, internet scale: simple management
No need to manage capacity
Data at rest encryption
Data in transit encryption by default from Google to endpoints
Online and offline import services available
Cloud Storage files are organized into buckets
Choosing a Cloud Storage class
3 ways to bring data into Cloud Storage
Online transfer: Self-managed copies using command-line tools or drag and drop
Storage Transfer Service: Managed batch transfers on a schedule
Transfer Appliance: A rackable appliance for securely transferring data
Can be used together with other GCP services
6. Cloud Bigtable
Features of Cloud Bigtable
Managed NoSQL
Fully managed NoSQL, wide-column database service for terabyte-scale applications
Access via HBase API
Natively compatible with big data and the Hadoop ecosystem
Replicated storage
Data encryption at all times, whether in use or not
Role-based ACLs
Powers key applications like Google Analytics, Gmail, and more
Bigtable access patterns
Application API You can read and write data in Cloud Bigtable through a data service layer such as managed VMs, an HBase REST server, or a Java server using an HBase client. This typically serves data to applications, dashboards, and data services.
Streaming You can stream data (write per event) through various popular stream processing frameworks such as Cloud Dataflow Streaming, Spark Streaming, and Storm.
Batch processing You can read and write data in Cloud Bigtable through batch processes such as Hadoop MapReduce, Dataflow, and Spark. Summary data or newly computed data is often written back to Cloud Bigtable or a downstream database.
7. Cloud SQL and Cloud Spanner
7-1. Cloud SQL
Features of Cloud SQL
Managed RDBMS
Offers MySQL and PostgreSQL databases as a service
Automatic replication
Managed backups
Vertical scaling (read and write)
Horizontal scaling (read)
Google security
Can be used with other GCP services
Cloud SQL + App Engine You can use Cloud SQL with App Engine through standard drivers. You can configure a Cloud SQL instance to follow an App Engine application.
Cloud SQL + Compute Engine You can authorize access to a Cloud SQL instance using the external IP address from a Compute Engine instance. You can configure a Cloud SQL instance in a preferred zone.
Cloud SQL + External Services You can use Cloud SQL with external applications and clients. You can manage databases using standard tools. You can configure external read replicas.
7-2. Cloud Spanner
Horizontally scalable RDBMS
Automatic replication
Strong global consistency
Highly available managed instances
SQL (ANSI 2011, with extensions)
8. Cloud Datastore
Horizontally scalable NoSQL DB
NoSQL designed for application backends
Fully managed: automatically handles scaling using a distributed architecture
Built-in redundancy
Supports ACID transactions
Schemaless access: no need to worry about the underlying data structure
Local development tools
Includes free daily quota
Accessible from anywhere via a RESTful interface
9. Comparing Storage Options
9-1. Technical Details
Cloud Datastore
Cloud Storage
Cloud Bigtable
Cloud SQL
Cloud Spanner
BigQuery
Type
NoSQL Document
Blobstore
NoSQL Wide-column
Relational SQL for OLTP
Relational SQL for OLTP
Relational SQL for OLAP
Transactions
O
X
Single-row
O
O
X
Complex queries
X
X
X
O
O
O
Capacity
TB+
PB+
PB+
10TB
PB
PB+
Unit size
1MB/entity
-10MB/cell -100MB/row
5TB/object
Determined by DB engine
10,240MiB/row
10MB/row
9-2. Use Cases
Cloud Datastore
Cloud Storage
Cloud Bigtable
Cloud SQL
Cloud Spanner
BigQuery
Type
NoSQL Document
Blobstore
NoSQL Wide-column
Relational SQL for OLTP
Relational SQL for OLTP
Relational SQL for OLAP
Best for
Semi-structured application data, durable key-value data
'Flat' data, heavy read/write, event and analytics data
Structured and unstructured binary or object data
Web frameworks, existing applications
Large-scale database applications (roughly 2TB+)
Interactive queries, offline analytics
Use cases
Getting started, App Engine applications
AdTech, finance, IoT data
Images, large media files, backups
User credentials, customer orders
Any case requiring high I/O and global consistency
댓글
Comments