🖤인프런만의 100% 블프 이벤트🖤

블로그

miiro

[인프런 워밍업 스터디 클럽 1기] BE 2주차 회고록

두 번째 발자국자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 두 번째 회고록입니다.학습 내용static이 아닌 코드를 사용하려면 인스턴스화를 해야합니다.하지만, 학습에서 진행했던 UserController는 jdbcTemplate를 의존하여 사용하고 있습니다.이를 가능케 하는 것은 @RestController 어노테이션으로 의해 의존이 가능해지고 해당 클래스를 스프링 빈으로 등록시킵니다. 스프링 빈서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너를 만듭니다.해당 컨테이너 안에는 클래스가 들어가게 되고, 다양한 정보(이름, 타입) 도 함께 들어가고, 인스턴스화도 이루어집니다.서버가 시작되면 코드의 구현 순서에 대해 살펴보겠습니다.스프링 컨테이너(즉, 클래스 저장소)가 시작됩니다.기본적으로 생성되어있는 스프링 빈들이 등록됩니다.사용자가 설정한 스프링 빈이 등록됩니다.필요한 의존성이 자동으로 설정이 됩니다. 여기서 스프링 컨테이너를 사용하는 이유를 살펴보면 2가지의 스프링 프레임워크의 특성 때문입니다.첫 번째는 제어의 역전(IOC) 이다. 말 그대로 메서드나 객체의 호출 작업을 개발자가 결정하는 것이 아닌, 외부(스프링 컨테이너)에서 결정되는 것을 의미입니다. 객체의 의존성을 역전시켜 객체간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 해서 가독성 및 코드 중복, 유지 보수를 편하게 할 수 있도록 도와줍니다.두 번째는 의존성 주입(DI) 로 객체를 직접 생성하는 것이 아닌 외부에서 생성할 후 주입시키는 방식을 의미한다.이를 통해 모듈 간의 결합도가 낮아지고 유연성을 높일 수 있습니다.  스프링 빈으로 등록하는 방법여기서 중점적으로 사용하는 어노테이션에 대해 살펴보겠습니다. @Configuration클래스에 붙이는 어노테이션@Bean을 사용할 때 함께 사용외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.@Bean메서드에 붙이는 어노테이션메서드에서 반환되는 객체를 스프링 빈에 등록한다.외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다.@Service, @Repository개발자가 직접 만든 클래스를 스프링 빈으로 등록할 때 사용한다.@Component주어진 클래스를 컴포넌트로 간주한다.해당 클래스들은 스프링 서버가 뜰 때 자동으로 감지된다. @Component 사용컨트롤러, 서비스, 레포지토리 X개발자가 직접 작성한 클래스를 스프링 빈으로 등록 시에 사용되기도 한다. 스프링 빈 주입 방법생성자를 활용하여 주입하는 방법setter와 @Autowired를 사용하는 방법필드에 직접 @Autowired를 사용하는 방법 JPA(Java Persistence API)데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 규칙영속성 : 서버가 재시작되어도 데이터는 영구적으로 저장되는 속성 ORM(Object-Relational Mapping)객체와 관계형 DB의 테이블을 짝짓는 방법HibernateJPA를 구현(implement)해서 코드로 작성한 구현체내부적으로 JDBC를 사용한다   JPA 어노테이션@Entity : 스프링이 객체와 테이블을 같은 것으로 바라본다.Entity 뜻 : 저장되고, 관리되어야 하는 데이터@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;@Id : 위 필드를 primary key로 간주@GeneratedValue : primary key는 자동 생성되는 값GenerationType.IDENTITY : MySQL의 auto_increment 전략과 매칭JPA를 사용하기 위해서는 기본생성자가 꼭 필요하다 @Column(nullable = false, length = 20, name = "name") private String name;@Column : 객체의 필드와 Table의 필드를 매칭한다.null 여부, 길이 제한, DB Column 이름 등을 사용@Column 어노테이션을 생략할 수도 있다.  트랜잭션(Transaction)쪼갤 수 없는 업무의 최소 단위 모든 SQL 한번에 성공 or 하나라도 실패하면 모두 실패 트랜잭션 명령어start transaction; : 트랜잭션 시작하기commit; : 트랜잭션 정상 종료(SQL 반영)rollback; : 트랜잭션 실패 처리(SQL 미반영)   트랜잭션 적용 방법@Transactional public void saveUser(UserCreateRequest request) { userRepository.save(new User(request.getName(), request.getAge())); }SELECT 쿼리만 사용한다면 readOnly 옵션 사용 가능@Transactional(readOnly = true) public List<UserResponse> getUsers() { return userRepository.findAll().stream() .map(UserResponse::new) .collect(Collectors.toList()); } ❗ 주의사항IOException 과 같은 Checked Exception은 롤백이 일어나지 않는다. 영속성 컨텍스트테이블과 매핑된 Entity 객체를 관리/보관하는 역할스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고, 트랜잭션이 종료되면 영속성 컨텍스트가 종료특징변경 감지(Dirty Check)영속성 컨텍스트 안에 불러와진 Entity는 명시적으로 save 하지 않더라도, 변경을 감지해 자동으로 저장된다.@Transactional public void updateUser(UserUpdateRequest request) { User user = userRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); user.updateName(user.getName()); }쓰기 지연DB의 insert/update/delete SQL문을 바로 날리는 것이 아닌, 트랜잭션이 commit될때 모아서 1번만 날린다.1차 캐싱ID를 기준으로 Entity를 기억한다.캐싱된 객체는 완전히 동일하다.(객체 주소도 같다) JPA 연관관계ex) 사람(person)과 실거주 주소(address)사람 1명은 1개의 실거주 주소만을 가지고 있다.@OneToOne연관관계의 주인을 설정한다.(mappedBy 사용한다.)연관관계의 주인 효과상대 테이블을 참조하고 있으면 연관관계의 주인이다.연관관계 주인이 아니면 mappedBy 사용연관관계의 주인의 setter가 사용되어야만 테이블이 연결@Transactional public void savePerson() { Person person = personRepotiory.save(new Person()); Address address = addressRepotiory.save(new Address()); person.setAddress(address); } 연관 관계의 주의점트랜잭션이 끝나지 않았기에, 한 쪽만 연결해두면 반대쪽의 값은 알 수 없다. @Transactional public void savePerson() { Person person = personRepotiory.save(new Person()); Address address = addressRepotiory.save(new Address ()); person.setAddress(address); System.out.println(address.getPerson); //null 반환 }이를 방지하기 위해 setter 한 번에 둘을 같이 이어준다.public void setAddress(Address address) { this.address = address; this.address.setPerson(this); } 다대일,일대다관계@ManyToOne을 단방향으로 사용할 수 있다.@JoinColumn연관관계의 주인이 활용할 수 있는 어노테이션필드의 이름이나, null 여부, 유일성 여부, 업데이트 여부 등을 지정할 수 있다.다대다관계(N : M 관계)@ManyToMany구조가 복잡하고, 테이블이 직관적으로 매핑되지 않기 때문에 사용하지 않는 것을 지양한다.cascade 옵션한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 흘러서 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능orphanRemoval 옵션객체간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션 @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List<UserLoanHistory> userLoanHistories = new ArrayList<>();지연 로딩(Lazy Loading)연결되어 있는 객체를 꼭 필요한 순간에 데이터를 로딩한다. @OneToMany의 fetch() 옵션 연관관계의 장점각자의 역할에 집중하게 된다.새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.테스트 코드 작성이 쉬워진다.연관관계 사용 시 주의 사항지나치게 사용하면 성능 상의 문제가 발생할 수 있다.도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.요구 사항 등 여러 부분을 고민해서 연관 관계 사용을 선택해야한다. 과제 내용Day 4Day2에서 진행했던 GET API와 POST API를 구현했었는데, 그 당시에는 database를 사용하지 않은 방법으로 과제에서 주어진 조건에 맞게 답을 출력했었습니다. 이번 과제에서는 DB를 활용하여 정보를 저장하고, 수정할 수 있는 로직을 구현해야했습니다.Layer를 분리해서 진행을 할 수 있지만, 간단하게 문제의 답만 도출하기 위해서 Controller Class에 구현을 진행했었습니다.그렇기 때문에 Spring Data JPA가 아닌 JDBCTemplate를 활용하여 진행했습니다. JPA를 통한 DB 로직을 구현하는 것만 주구장창 했어서, 직접 쿼리문을 작성해서 진행하는 JDBCTemplate를 사용하는 방법에 대해 알 수 있었던 거 같습니다.📋 4일차 미션 : GET API와 POST API 구현 Day 5클린 코드는 코드를 아름답게 만드는 것이 아닌 코드의 질을 향상 시키는 데 중요하다는 것을 알았습니다.클린 코드를 구현하기 위해서 명확하고 간결하게 코드를 구현하여 버그를 줄이고, 그로 인한 개발 속도를 향상시킬 수 있습니다.또한, 잘 작성된 코드는 시간이 흘러도 이해하기 쉽기에, 유지 보수가 간편합니다. 이로 인해 개발자로서 팀 프로젝트를 진행할 시에팀 커뮤니케이션을 원활하게 진행할 수 있도록 도움을 줄 수 있다는 것을 알게 되었습니다.📋 5일차 미션 : 클린 코드 구현  회고스프링 프레임워크의 핵심 개념인 제어의 역전(IOC)과 의존성 주입(DI)을 통해 더 나은 코드 작성과 유지 보수의 용이섬에 대해 알게 되었습니다. 또한 어노테이션들을 활용하여 클래스를 스프링 빈으로 등록하고, 스프링 컨테이너가 이를 관리함으로써, 필요한 의존성을 자동으로 설정할 수 있다는 사실을 배웠습니다. JPA를 통한 데이터의 영속성 관리와 ORM을 통한 객체와 DB의 매핑에 대해 이론적으로 정립할 수 있는 계기되었던 거 같습니다.서비스를 구성하는 데 가장 중요한 @Transactional 어노테이션을 사용하여 데이터의 일관성을 유지하는 방법을 쉽게 공부하면서 더티 체크, 쓰기 지연 등 특성에 대해 알 수 있었던 거 같습니다.객체와 DB를 효과적으로 설계할 수 있도록 연관관계를 설계하는 방법을 배움으로써 사용할 때의 주의사항과 성능 문제를 방지하기 위한 전략에 대해 알게 되면서 이를 통해 효과적이고 용이한 애플리케이션 개발을 할 수 있는 토대를 마련할 수 있었던 거 같습니다.  

백엔드인프런워밍업스터디클럽2주차회고록

bananachacha

[인프런 워밍업 스터디 클럽 0기] 첫 번째 발자국

강의 출처https://inf.run/XKQg 1주차 학습 내용 요약 Day 1: 개발 환경 설정 및 네트워크 기초스프링 프로젝트를 만들고, 웹에서 데이터를 주고 받는 방법에 대해 이해했습니다.API를 만들기 전에 API 설계를 어떻게 하는지 배우고, 그것을 기반으로 스프링의 어노테이션을 사용하여 실제 API를 개발했습니다. Day 2: 첫 HTTP API 개발클라이언트로부터 JSON 형식의 데이터를 받아와서 처리하는지에 대한 기본 코드를 작성하면서, 실제 도서 관리 애플리케이션을 만들어보았습니다. Day 3: 기본적인 데이터베이스 사용법디스크와 메모리의 차이를 이해하고, 간단한 엑셀로 비유를 통해 MySQL이라는 데이터베이스를 어떻게 다루는지 학습했고, MySQL을 사용하여 데이터베이스를 조작하는 기초적인 CRUD 작업을 익혔습니다.또한, 스프링 서버에서 실제로 데이터베이스에 접근하도록 설정 파일을 작성하고 Jdbctemplate을 이용한 데이터베이스 연동을 학습했습니다. Day 4: 데이터베이스를 사용해 만드는 API요청 값의 검증과 예외 처리에 대한 로직을 작성하여 안정적으로 업데이트와 삭제 API를 개발했고, 스프링 서버가 데이터를 저장, 조회, 업데이트, 삭제할 수 있는 모든 기능을 구현했습니다. Day 5: 클린 코드의 개념과 첫 리팩토링클린 코드의 중요성과 코드를 깔끔하게 유지하기 위해 Layered Architecture를 도입하여 Controller, Service, Repository로 3단 분리하여 코드를 리팩토링하는 방법에 대해 배웠습니다.이를 통해 코드의 가독성과 유지보수성을 향상시키는 방법을 알게 되었습니다.  1주차 미션 Day1 어노테이션을 사용하는 이유(효과)는 무엇일까?나만의 어노테이션은 어떻게 만들 수 있을까? 어노테이션에 대해 학습하여 해당 질문에 대한 답변을 스스로 작성해볼 수 있는 미션이었습니다.평소에 스프링에서 어노테이션을 사용할 때 그냥 사용하면 되는구나 정도로만 알고 있었는데, 어노테이션의 기본 문법부터 커스텀 어노테이션까지 학습하면서 어노테이션의 역할과 장점을 이해할 수 있었습니다.보일러 플레이트 코드를 줄여주는 이점과 애플리케이션의 고유한 요구사항에 맞게 커스텀한 어노테이션의 사용이 개발 생산성에 도움을 줄 수 있다는 점도 알게 되었습니다.이제는 개념들을 단순히 사용하는 것이 아니라, 그 뒤에 숨은 이유와 장점을 고려하여 개발에 적용할 수 있게 되었습니다. 블로그https://velog.io/@awesomehill/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%B2%AB-%EB%B2%88%EC%A7%B8-%EA%B3%BC%EC%A0%9C  Day2 GET API와 POST API 개발하기 문제에 제시된 API Spec을 보고 API를 개발하는 미션이었습니다.먼저, GET API를 개발할 때 쿼리 파라미터의 처리 방법에 대해 고민하였습니다.쿼리 파라미터의 개수가 한 개인 경우에는 변수에 바로 바인딩하는 방식을 선택하였고, 여러 개의 쿼리 파라미터가 필요한 경우에는 Dto 클래스를 활용하여 요청을 매핑하는 방식을 선택했습니다.POST API를 개발할 때에는 Request Body로 전송되는 JSON 데이터를 받아오기 위해 Request Dto를 만들고, 이를 통해 데이터를 매핑하는 코드를 작성했습니다.이때, Java 14에서 추가된 record를 활용하여 Dto를 불변 객체로 만들어 코드의 안정성을 높였습니다.또한, Java 8 이후로 도입된 LocalDate 클래스를 사용하여 Date 클래스와의 차이점과 날짜 데이터를 처리하는 방법을 익힐 수 있었습니다. 블로그https://velog.io/@awesomehill/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%91%90-%EB%B2%88%EC%A7%B8-%EA%B3%BC%EC%A0%9C  Day3 자바의 람다식은 왜 등장했을까?람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까? 람다식이 등장한 이유와 익명 클래스와의 관계에 대해 알아보면서 해당 질문에 대한 답변을 생각할 수 있는 미션이었습니다.기존에는 익명 클래스를 사용하여 함수형 인터페이스를 구현하는데 많은 코드를 필요로 했으며, 이는 작은 작업을 수행하는 경우에도 코드의 양이 불필요하게 많아지는 문제를 야기했습니다.하지만, 람다식의 등장으로 이러한 불편함을 해소하고, 간단한 표현으로 함수형 인터페이스를 구현할 수 있게 되었습니다.람다식이 어떻게 코드를 간결하게 만들어주는지를 실제 코드를 통해 경험하면서 개념을 익힐 수 있었습니다. 블로그https://velog.io/@awesomehill/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%84%B8-%EB%B2%88%EC%A7%B8-%EA%B3%BC%EC%A0%9C  Day4 데이터베이스를 사용해 API 개발하기 JdbcTemplate을 활용하여 데이터베이스와 상호 작용하는 방법에 대해 깊게 학습했습니다.필요한 테이블을 생성하고 JdbcTemplate을 사용하여 SQL 쿼리를 실행하여 데이터를 다루는 방법을 익혔습니다. 예제를 따라가면서 HTTP 요청에 따라 데이터베이스에 새로운 데이터를 추가하거나 업데이트하는 코드를 작성하면서 JdbcTemplate의 실제 활용도를 체감할 수 있었습니다.테이블에 is_sold 칼럼을 추가하여 데이터의 판매 여부를 명시함으로써 데이터베이스의 일관성과 코드의 가독성을 향상할 수 있었습니다.그리고 문제 3에서 SUM이나 GROUP BY 등의 집계 함수에 대한 정확한 이해가 필요하다고 느껴, 틈틈히 SQL 공부를 병행해야 겠다는 생각이 들었습니다. 블로그https://velog.io/@awesomehill/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%84%A4-%EB%B2%88%EC%A7%B8-%EA%B3%BC%EC%A0%9C  Day5 클린 코드 개념을 적용하여 코드 리팩토링하기 클린 코드 작성하는 방법에 대해 찾아보고 과제에 제시된 코드를 리팩토링하는 미션이었습니다.코드를 입력, 실행, 출력 단계로 명확히 분리하여 가독성을 향상하여 Main 클래스에서 코드의 흐름을 쉽게 이해할 수 있도록 설계했습니다.코드를 읽는 개발자가 다른 개발자의 의도를 이해하는 데 도움이 될 수 있도록 메소드명과 변수명을 코드의 목적과 기능을 명확히 드러낼 수 있도록 작성했습니다.코드를 작게 만들어 가독성을 높이고, 재사용성을 증가시키기 위해 클래스와 메소드는 하나의 책임만을 갖도록 설계했습니다.상수를 활용하여 반복되는 문자열을 상수로 정의하여 코드의 일관성을 유지하고 오타를 방지했습니다.주사위를 나타내는 클래스에서 OOP 개념을 도입하여 유연성을 높이고, 나중에 주사위 범위를 변경해도 코드의 수정이 용이하도록 구조화했습니다.이번 미션을 통해 클린 코드 작성의 중요성과 가독성의 영향력을 체감할 수 있었습니다.코드를 작성하는 것뿐만 아니라, 코드를 읽는 것이 개발에 있어서 중요한 부분임을 깨닫게 되었습니다.  블로그https://velog.io/@awesomehill/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-0%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%8B%A4%EC%84%AF-%EB%B2%88%EC%A7%B8-%EA%B3%BC%EC%A0%9C  1주차 회고 프로젝트를 진행하면서 스프링 JPA에 대한 학습을 병행했는데, 조금은 힘든 도전이었지만, 그만큼 성취감과 만족도도 크게 느낄 수 있었습니다.또한, 인프런 스터디를 통해 다양한 주제에 대한 학습과 토론을 할 수 있어 지식의 폭을 넓히는 데에 도움이 되었습니다.여러 일을 병행하면서 힘들기도 했지만, 이러한 경험이 성장의 발판이 될 것이라고 생각합니다.앞으로도 끝까지 완주할 수 있도록 노력하겠습니다:)

웹 개발인프런워밍업스터디클럽발자국

miiro

[인프런 워밍업 스터디 클럽 1기] BE 4주차 회고록

네 번째 발자국자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 네 번째 회고록입니다.학습 내용build.gradle빌드 스크립트라고 불린다.gradle을 이용하여 프로젝트를 빌드하고 의존성을 관리하기 위해 작성되었다.현재는 groovy 언어를 사용해 작성되어있고, kotlin 언어를 사용할 수도 있다. 플러그인plugins { id 'java' id 'org.springframework.boot' version '3.1.11' id 'io.spring.dependency-management' version '1.1.4' } org.springframework.boot 플러그인스프링을 빌드했을 때 실행 가능한 jar 파일이 나오게 도와준다.스프링 애플리케이션의 실행을 도와준다.이 외의 플러그인들이 적용될 수 있도록 해준다.io.spring.dependency-management 플러그인외부 라이브러리, 프레임워크의 버전 관리의존성 처리하는 데 사용java 플러그인java 프로젝트 개발 시 필요한 기능 추가JVM 언어 Gradle 플러그인의 사용할 수 있는 기반 마련 그룹group = 'sample' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' }group프로젝트 그룹빌드 결과물에 프로젝트 그룹에 대한 정보가 들어있다.version프로젝트 버전sourceCompatibility프로젝트가 사용하고 있는 JDK 버전 레포지토리repositories { mavenCentral() }외부 라이브러리/ 프레임워크를 가져오는 장소 설정 의존성(dependencies)dependencies { // Spring boot implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' //mysql runtimeOnly 'com.mysql:mysql-connector-j' }사용하는 라이브러리/프레임워크 표시implementation : 해당 의존성을 항시 사용한다.runtimeOnly : 코드를 실행할때에만 의존성을 사용한다.testImplementation : 테스트코드를 컴파일하거나 실행할 때 항시 사용한다.Spring과 SpringBoot의 차이점간편한 설정스프링은 강력한 기능을 제공한다.ex) IoC/DI, AOP, PSA ..기능을 사용하기 위해 xml 설정을 해야했는데 양이 많았다. -> 어노테이션 기반의 설정으로 변경하고 기본적으로 필요한 것들은 모두 자동 설정간단한 의존성 관리Spring을 사용할 때는 개발에 필요한 라이브러리/프레임워크를 모두 기입해야했다.의존성 관리가 힘들기에 starter로 묶어서 관리강력한 확장성Springboot 내부에 tomcat이 있기에 따로 설정할 필요가 없다.MSA에 적합한 모니터링application.yml 파일YAML 문법Yet Another Markup Language(또 다른 마크업 언어)YMAL Ain't Markup Language(마크업 언어가 아니다)확장자 : .yaml 또는 .ymlkey : value 형식으로 데이터를 정의한다.각 계층은 들여쓰기를 통해 구분하게 되며, 이를 통해 중복을 제거할 수 있다.value에는 참/거짓, 숫자, 문자열이 들어갈 수 있다.value에는 배열도 들어갈 수 있다. - 를 활용한다.#을 이용하면 주석을 사용할 수 있다.application.propertiesdev profile 사용application-dev.properties 파일 생성Lombokgetter/setter, 생성자와 같은 반복되는 보일러 플레이트 코드(boiler plate code)를 제거할 수 있다.dependencies 추가compileOnly 'org.projectlombok:lombok'annotationProcessor 추가annotationProcessor 'org.projectlombok:lombok'Spring Boot 2.7.x -> 3.0.x 변경점java 최소 버전이 17로 업그레이드스프링 프로젝트, third-party library 버전 업그레이드AOT 기초 작업AOT(Ahead Of Time) : 빌드 시 스프링 애플리케이션을 분석하고 최적화하는 도구애플리케이션 시작 시간과 메모리 사용량을 줄일 수 있게 해준다.javax -> jakarta 패키지로 변경모니터링 기능 강화 과제 내용이번 프로젝트에서는 출퇴근 사내 시스템으로써, 회사 내 팀과 직원 관리, 출퇴근 관리, 그리고 연차 관리 기능을 설계하였습니다. 기본적인 기술 스택으로는 Java 17 / Springboot 3.x.x으로 구성하여 팀 관리에서는 팀 이름을 필수로 등록하고 팀 정보를 조회할 수 있는 기능을 구현했습니다. 직원 관리에서는 직원의 이름, 매니저 여부, 입사 날짜, 생일을 등록하고, 직원 정보를 조회할 수 있게 했습니다. 출퇴근 관리에서는 직원의 출근 및 퇴근 기능을 구현하였으며, 직원의 근무 시간을 날짜별로 조회할 수 있도록 하였습니다. 연차 관리에서는 직원이 연차를 신청하고, 남아있는 연차를 조회할 수 있는 기능을 제공했습니다. 특히, 연차 신청 시 팀마다 다른 등록 기간을 적용하여 유연성을 높였습니다. 📋 미니 프로젝트 Github : commuting-system회고이번 프로젝트를 통해 팀, 직원, 출퇴근, 연차 관리를 통합적으로 설계하는 경험을 쌓았습니다. 팀 관리에서는 팀의 필수 정보를 명확히 정의하고, 팀 조회 기능을 통해 팀 운영의 투명성을 높였습니다. 직원 관리에서는 직원의 기본 정보를 체계적으로 관리할 수 있게 되었고, 매니저 여부와 같은 역할 분류를 통해 조직의 구조를 명확히 파악할 수 있었습니다. 출퇴근 관리에서는 출퇴근 예외 처리와 날짜별 근무 시간 조회 기능을 통해 직원들의 근태를 정확히 관리할 수 있었습니다. 연차 관리에서는 연차 신청과 조회 기능을 통해 직원들이 연차를 효율적으로 사용할 수 있도록 했습니다. 팀마다 다른 연차 등록 기간을 적용함으로써 조직의 유연성을 높였습니다. 이 프로젝트를 통해 데이터의 일관성을 유지하면서도 다양한 요구 사항을 충족시키는 설계의 중요성을 깨달았습니다. 또한, 시스템 통합의 복잡성을 이해하고, 사용자 편의성을 고려한 기능 설계의 필요성을 다시 한 번 느꼈습니다.

백엔드인프런워밍업스터디클럽4주차회고록

짙은

[인프런 워밍업 스터디 클럽 1기] 백엔드 3주차 회고록

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 회고록입니다.Plus 😍공휴일이 있어, 상대적으로 여유롭게 느껴졌던 한 주였다. 과제도 너무 늦지 않게 업로드할 수 있어 마음이 편안했다.Minus 🥶지난 주 아쉬웠던 점이 '시간이 부족해 과제를 급하게 제출했다' 였는데, 지난 주 과제를 디벨롭하면서 예외 처리가 거의 되어있지 않다는 사실을 발견했다. 급하게 하는 일들은 어떻게든 문제가 생긴다. 잊지말자.강의 중 실습이 필요한 강의는 컴퓨터로, 그렇지 않은 강의는 휴대폰으로 시청하는 경우가 많은데 나에게는 크게 실습이 의미가 없는 강의들 (aws, git 등) 을 휴대폰으로 수강하려고 했으나, 까만화면만 보이고 재생이 되지 않는 문제가 있었다... 심지어 같은 환경에서 일부 다른 강의는 재생이 되는 것을 확인했다. 그래서 휴대폰으로 봐도 문제없을 강의들을 컴퓨터로 수강해야만 했다. 컴퓨터로 수강하는게 좋고 나쁘고의 문제라기보다는, 개인 시간을 이렇게 활용해야겠다고 세웠던 계획이 있었는데 그대로 하지 못한게 (그것도 외부 요인 때문에) 아쉬웠다.Interesting 🤔개발 능력에 대한 메타인지 그래프를 그려보자.  

백엔드인프런워밍업스터디클럽

miiro

[인프런 워밍업 스터디 클럽 1기] BE 3주차 회고록

세 번째 발자국자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 세 번째 회고록입니다. 학습 내용배포란?최종 사용자에게 SW를 전달하는 과정이다. = 전용 컴퓨터에 우리의 서버를 옮겨 실행시키는 것서버용 컴퓨터는 보통 리눅스를 사용한다.profile과 H2 DBprofile : 똑같은 서버 코드를 실행시키지만, 실행될 때 설정을 다르게 하고 싶다.(ex. Database)[예시]local profile -> H2 DBdev profile -> MySQL DB  H2 DB경량 database로, 개발 단계에서 많이 사용하며 디스크가 아닌 메모리에 데이터를 저장할 수 있다.메모리에 데이터를 저장하면 휘발되는 특징으로 인해 개발 단계에서만 사용한다개발단계에서는 테이블이 계속 변경되는데, 데이터가 휘발되기에 ddl-auto 옵션을 create로 주면 테이블이 자동 생성/삭제가 되기에 주로 사용된다.git/githubgit코드를 쉽게 관리할 수 있도록 해주는 버전 관리 프로그램githubgit으로 관리되는 프로젝트의 코드가 저장되는 저장소사용하는 이유?코드는 어떠한 이유로든 소실이 될 가능성이 있다.배포를 할 때 활용할 수 있다.git 명령어git init해당 프로젝트를 git이 관리하겠다는 의미git 프로젝트의 github 저장소 설정git remote add origin [git 주소]코드를 git에 모든 파일을 넣는다git add .Git 커밋 명령어git commit -m '메시지'Git 푸시 명령어git push코드 github에 최초로 보내기git push --set-upstream origin masterGit 상태 확인git status.gitignore파일 안에 적힌 폴더 및 파일의 이름은 깃으로 관리하지 않는다. EC2에 접속해서 리눅스 명령어 다뤄보기 EC2 접속 방법다운로드 받은 키 페어(pem key)를 이용하는 방법접속하려는 EC2의 IP 주소다운로드 받은 키 페어 접속하기 위한 프로그램(git CLI or Mac Terminal) chmod 400 키페어이름.pemssh -i 경로/키페어이름.pem ec2-user@IPAWS 콘솔을 활용해서 접속하는 방법 기본적인 리눅스 명령어mkdir : 폴더를 만드는 명령어ex) mkdir folder1ls : 현재 위치에서 폴더나 파일을 확인하는 명령어ls -l : 조금 더 자세한 정보를 확인할 수 있다. drwxrwxr-x : folder1은 폴더이다.drwxrwxr-xr : 읽는 권한, w : 쓸 수 있는 권한, x : 실행 권한rwx/rwx/r-x폴더 소유자 권한 / 폴더 소유 그룹 권한/ 모든 접근의 권한2 : 폴더에 걸려 있는 바로가기 개수ec2-user : 폴더 소유주의 이름ec2-user : 폴더 소유 그룹의 이름6 : 폴더(파일의 크기) 단위 : byte cd(change directory) : 폴더 안으로 들어가는 명령어ex) cd folder2pwd(print working directory) : 현재 위치 확인하는 명령어/home/ec2-user/folder1cd .. : 상위 폴더로 올라가는 명령어rmdir : 특정 폴더(디렉토리) 제거하는 명령어 배포를 위한 프로그램 설치하기 코드를 가져오기 위한 Gitsudo yum install git : yum을 이용한 프로그램 다운로드 진행한다.서버를 구동할 Javasudo yum install java-11-amazon-corretto -yjava -version : 자바 버전 확인데이터베이스 MySQLsudo dnf install https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpmsudo dnf install mysql-community-serversudo systemctl status mysqldsudo systemctl restart mysqldsudo cat /var/log/mysqld.log | grep "A temporary password"mysql8의 임시 비밀번호를 확인하는 명령어alter user 'root'@'localhost' identified with mysql_native_password by 'Abdc1234!';비밀번호는 대/소문자, 특수문자 포함 8자 이상 리눅스에서 스프링 배포 프로그램 설치sudo yum updatesudo : 관리자 권한 실행yum : 리눅스 패키지 관리 프로그램(ex. gradle과 비슷한 역할)update : 현재 프로그램들을 최신화 설정한다. 빌드와 실행, 그리고 접속 git 코드 로딩 :git clone [github 저장소 주소]swap 설정기존에는 RAM을 사용해야하지만 RAM의 용량이 부족한 경우, 일부 Disk를 사용하게 해준다.#swap 메모리를 할당한다. (128M 16 = 2GB) sudo dd if=/dev/zero of=/swapfile bs=128M count=16 #스왑 파일에 대한 권한 업데이트 sudo chmod 600 /swapfile #swap 영역 설정 sudo mkswap /swapfile #swap 파일을 사용할 수 있도록 만든다. sudo swapon /swapfile #swap 성공 확인 sudo swapon -schmod +x ./gradlewgradlew를 사용하기 위해 실행할 수 있도록 설정한다../gradlew build -x testgradle을 이용해 프로젝트를 빌드한다. -> 테스트코드는 실행하지 않는다. java -jar build/libs/library-app-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev--spring.profiles.active=dev : 설정 파일등록 이후에 인스턴스 인바운드 규칙 설정을 해줘야한다.8080 포트 Open  jar 파일만 실행했는데 서버가 동작하는 이유?SpringBoot 에는 톰캣(Tomcat)이 내장되어 있기 때문이다.톰캣(Tomcat)웹 애플리케이션 서버(WAS)의 한 종류로써, 요청이 들어오면 그 요청을 약속된 형식에 맞추어 스프링에 전달해준다.실행 중인 서버 중단ctrl + c./gradlew clean현재 빌드되어 있는 결과물 제거 foreground vs background foreground우리가 보고 있는 프로그램ex) PDF 프로그램이 foreground 프로그램 background우리가 보고 있지 않은데 실행중인 프로그램ex) 컴퓨터에서의 백신 프로그램 리눅스에서 background로 동작하게 만드는 명령어nohup [명령어] &nohup java -jar build/libs/library-app-0.0.1-SNAPSHOT.jar &rm nohup.out  background 서버 다운작업관리자 명령어ps aux : 현재 실행중인 프로그램 목록을 확인할 수 있다.ps aux | grep java : java가 들어가는 프로그램을 확인한다.kill -9 프로그램번호 : 해당 프로그램을 종료시킨다.파일의 내용물을 확인하는 방법파일에 직접 들어가 내용물 확인vi : 리눅스 편집기인 vim을 사용하여 파일을 접속한다. (ex. vi nohup.out)파일에 들어가지 않고 현재 접속중인 터미널을 활용하는 방법cat : 파일에 있는 내용물을 모두 출력하는 명령어(ex. cat nohup.out)파일 내용물의 양이 많지 않고, 실시간 업데이트가 잘 되지 않는 파일을 확인할 때 사용한다.tail : 현재 파일의 끝 부분을 출력하는 명령어tail -f : 현재 파일의 끝부분을 실시간으로 출력해준다.  과제 내용Day 6Controller, Service, Repository의 3단 분리 구조를 채택하여 백엔드 설계를 진행했습니다.Repository 클래스에서는 jdbcTemplate를 활용해 SQL문을 직접 작성하고 데이터베이스와 직접적으로 연결하는 방식을 택했습니다. 이 과정에서 @Repository 어노테이션을 활용한 생성자를 통한 의존성 주입이 이루어지는 것을 확인할 수 있었습니다.특히, 다양한 저장소 구현체(FruitMySqlRepository, FruitMemoryRepository)를 만들면서, @Primary 어노테이션을 활용하여 서비스 로직이 실행될 때 어떤 레포지토리 레이어를 우선적으로 사용할지 결정하는 방식을 경험했습니다. 이러한 구조적 접근은 유연하고 확장 가능한 백엔드 시스템을 구축하는 데 큰 도움이 되었습니다.📋 6일차 미션: 레이어 3단 분리 Day 7JDBCTemplate을 사용해 시스템을 구축한 DB를 성능 개선과 유지보수의 편의성을 위해 JPA로 전환하였습니다.엔티티 클래스인 Fruit에서는 과일의 이름, 입고 날짜, 가격, 판매 여부를 관리했습니다. JPA의 강력함은 FruitRepository 인터페이스를 통해 드러났다. 여기서 단순 조회부터 복잡한 조건의 데이터 처리까지 어노테이션 몇 개로 해결할 수 있었습니다. FruitServiceV2에서는 과일의 저장, 판매 상태 업데이트, 통계 조회 등 핵심 비즈니스 로직을 구현하여, 코드의 간결함과 효율성을 극대화했습니다.특히, 과일의 개수를 세거나, 특정 가격 이상 또는 이하의 과일을 조회하는 기능을 추가하며, JPA의 유연성과 편리함에 다시 한번 감탄했습니다. 이 모든 과정을 통해, 나는 JPA의 놀라운 잠재력을 경험하며, 보다 나은 소프트웨어 개발자로 성장할 수 있는 계기를 마련한 것 같습니다.📋 7일차 미션: JPA 구현회고SW 배포는 최종 사용자에게 소프트웨어를 전달하는 과정으로, 리눅스 기반 서버 컴퓨터에서 주로 이루어집니다. 개발 단계에서는 경량화된 H2 DB를 활용하여 데이터의 휘발성을 감안한 개발 환경을 구축합니다. Git과 GitHub을 통한 버전 관리 및 코드 저장소 활용은 코드 소실 방지와 효율적인 배포 과정을 지원합니다. EC2와 리눅스 명령어를 통한 서버 접속 및 관리, 그리고 리눅스 환경에서의 스프링 배포는 필수적인 프로세스라는 것을 알게 되었습니다.또한, Controller, Service, Repository의 3단 분리 구조를 채택하여 백엔드 설계를 진행하고, JDBCTemplate에서 JPA로 전환하여 성능 개선과 유지보수의 편의성을 높였습니다. 이 모든 과정은 유연하고 확장 가능한 백엔드 시스템 구축과 개발자로서의 성장에 밑거름이 된 거 같습니다.

백엔드인프런워밍업스터디클럽3주차회고록

임하스

[인프런 워밍업 스터디 클럽 1기] FE 2주차 발자국

시작강의가 좀 밀려있긴 한데... 기간 안엔 다 들을 수 있을 거라 생각하며 차근차근 정리 중이다...정리자바스크립트 this 키워드메서드 내 this ⇒ 해당 객체(Object)함수 내 this ⇒ window 객체생성자 함수 내 this ⇒ 빈 객체(해당 인스턴스)forEach문 내 this ⇒ 두 번째 매개 변수 값, 없을 경우 window 객체 또는 해당 객체화살표 함수 내 this ⇒ 상위 스코프의 this예제메서드 내 ⇒ 해당 객체(Object)const audio = { title: 'a', play() { console.log('play this', this); }, }; audio.stop = function () { console.log('stop this', this); }; audio.play(); //play this { title: 'a', play: f } audio.stop(); //play this { title: 'a', play: f } 함수 내 ⇒ window 객체function playAudio() { console.log(this); } playAudio(); // Window 객체 생성자 함수 내 ⇒ 빈 객체(해당 인스턴스)function Audio(title) { this.title = title; // 이게 없으면 빈 객체 console.log(this); } const audio = new Audio('a'); // Audio { title: 'a' } forEach문 내 ⇒ 두 번째 매개 변수 값, 없을 경우 window 객체 또는 해당 객체const audio = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach(function (category) { console.log(`title: ${this.title}, category: ${category}`); // 함수 내에 있기 때문에 window 객체를 가리킴 }); }, }; audio.displayCategories(); const audio2 = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach( function (category) { console.log(`title: ${this.title}, category: ${category}`); }, { title: 'audio' } ); // forEach 두 번째 매개변수에 있는 값을 참조시킬 수 있음 }, }; audio2.displayCategories(); const audio3 = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach( function (category) { console.log(`title: ${this.title}, category: ${category}`); }, this // audio3 해당 객체를 참조 ); }, }; audio3.displayCategories(); 화살표 함수 내 ⇒ 상위 스코프의 thisconst audio = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach(function (category) { console.log(`title: ${this.title}, category: ${category}`); // 상위 스코프의 this(Lexical this) => audio { title : 'audio', categories: Array(3) } }); }, }; audio.displayCategories(); call, apply, bind함수에서 this 사용 시, window 객체가 아닌 값을 참조시키기 위해 사용함수명.call(thisArg[, arg1, arg2, ...]) : 함수를 호출하는 함수함수를 호출할 때, 첫 번째 매개 변수에 함수 내에서 사용할 this 값을 지정할 수 있음첫 번째 매개 변수로 null 또는 undefined가 전달되면 전역 객체가 this로 사용됨두 번째 ~ … 매개 변수로 호출할 함수에 전달될 인수 추가 가능const fullName = function (arg1, arg2) { console.log(`${this.firstName} ${this.lastName}`); console.log(`${arg1} ${arg2}`); }; const person = { firstName: 'Im', lastName: 'Hass', }; fullName.call(person, '매개변수1', '매개변수2'); // Im Hass // 매개변수1 매개변수2 함수명.apply(thisArg[, argArray]) : call()과 동일call()과 인수를 배열로 넣어주는 차이const fullName = function (arg1, arg2) { console.log(`${this.firstName} ${this.lastName}`); console.log(`${arg1} ${arg2}`); }; const person = { firstName: 'Im', lastName: 'Hass', }; fullName.apply(person, ['매개변수1', '매개변수2']); **함수명.bind(thisArg)**: 함수가 실행되지 않고 바인딩만 시켜줌함수를 바인딩할 때, 새로운 함수를 생성함 → 원본 함수와 동일한 코드를 가지지만 this가 바인딩된 값이 들어감 ⇒ 새로운 변수에 담거나 즉시 실행 시켜서 사용해야 함function func(language) { if (language === 'kor') { console.log(`language: ${this.korGreeting}`); } else { console.log(`language: ${this.engGreeting}`); } } const greeting = { korGreeting: '안녕', engGreeting: 'Hello', }; // 원본은 변하지 않기 때문에 undefined 출력 // func.bind(greeting); // func('kor'); // language: undefined // 1. 새 변수에 담아서 사용하기 // const boundFunc = func.bind(greeting); // boundFunc('kor'); // language: 안녕 // 2. 즉시 실행하기 func.bind(greeting)('kor'); Event Loop자바스크립트는 동기 언어지만, 다른 것의 도움(브라우저의 자바스크립트 엔진 또는 Node.js와 같은 런타임 환경)을 통해 비동기로 처리할 수 있음.ex) setTimeout(브라우저 api-window object 또는 node api-global object)setTimeout에 설정된 값이 0(즉시 실행)이어도 콜백 큐에서 호출 스택이 비어야 들어가기 때문에 순서가 다르게 출력됨자바스크립트 코드를 실행하기 위해 엔진이 필요한데, 자바스크립트 엔진에는 메모리 힙, 콜 스택 두 가지 주요 구성 요소가 있음Call Stack(호출 스택)코드 실행에 사용되는 메모리 구조함수 호출 및 반환을 추적함코드가 실행될 때 함수 호출은 스택에 추가되고, 함수가 완료되면 스택에서 제거됨Callback Queue(콜백 큐)비동기 작업의 콜백 함수들이 대기하는 대기열비동기 작업이 완료되면 해당 콜백 함수가 콜백 큐에 추가됨Event Loop(이벤트 루프)콜 스택과 콜백 큐를 계속 모니터링하여 콜 스택이 비어있을 때 콜백 큐의 첫 번째 콜백을 콜 스택으로 이동시킴내부 진행 참고 사이트 : https://kamronbekshodmonov.github.io/JELoop-Visualizer/Closure외부 함수 보다 생명 주기가 긴 내부 함수 중에서, 종료된 외부 함수의 값을 참조할 수 있는 내부 함수를 의미한다.클로저 사용 전(오류: b에 접근 불가)let a = 'a'; function funcB() { let c = 'c'; console.log(a, b, c); } function funcA() { let b = 'b'; console.log(a, b); funcB(); } funcA(); // a b // ReferenceError: b is not defined 클로저 사용 후(해결: 내부 함수로 변경)let a = 'a'; function funcA() { let b = 'b'; console.log(a, b); function funcB() { let c = 'c'; console.log(a, b, c); } funcB(); } funcA(); // a b // a b c 구조 분해 할당배열이나 객체의 속성을 해체하여 개별 변수에 담을 수 있게 하는 Javascript 표현식객체 구조 분해 할당let person = { name: 'hass', age: 30, phone: '123', address: { zipcode: 1234, street: 'rainbow', number: 42, }, }; let { address: { zipcode, street, number } } = person; console.log(zipcode, street, number); 별명 지정let people = [ { name: 'mike', age: 35, family: { mother: 'Jane', father: 'Harry', sister: 'samantha', }, }, { name: 'Tom', age: 25, family: { mother: 'Norah', father: 'Richard', brother: 'Howard', }, }, ]; for (let { name: n, family: { father: f }, } of people) { console.log(`Name : ${n}, Father: ${f}`); } // Name : mike, Father: Harry // Name : Tom, Father: Richard Map() 메서드배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환함array.map(callback(currVal[, index[, array]]])[, thisArg])첫 번째 매개 변수로 callback 함수가 들어가고, 두 번째 매개 변수에 this 바인딩 가능Filter() 메서드주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환함array.filter(callback(element[, index[, array]])[, thisArg])첫 번째 매개 변수로 callback 함수가 들어가고, 두 번째 매개 변수에 this 바인딩 가능Reduce() 메서드배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환함arr.reduce(reducerFunction(acc, curr, idx, src), initalValue)reducerFunction 4개의 인자를 가짐누산기(acc)현재 값(curr)현재 인덱스(idx)원본 배열(src)예제const result = [0, 1, 2, 3, 4].reduce(function (acc, curr, idx, src) { return acc + curr; }, 10); // console.log(result); // 20 acc curr idx src return value 1번째 호출 10 0 0 [0, 1, 2, 3, 4] 10 2번째 호출 10 1 1 [0, 1, 2, 3, 4] 11 3번째 호출 11 2 2 [0, 1, 2, 3, 4] 13 4번째 호출 13 3 3 [0, 1, 2, 3, 4] 16 5번째 호출 16 4 4 [0, 1, 2, 3, 4] 20  undefiend vs null공통점둘 다 원시 자료형undefiend의 타입은 undefiend, null 타입은 null(object로 나옴, strit 모드는 null) 유일한 타입undefined아무 값도 할당받지 않은 상태개발자의 의도 X, 자바스크립트 엔진이 초기화한 값null비어있는 값, 존재하지 않는 값개발자가 의도한 값null을 할당하면 변수가 이전에 참조하던 값을 명시적으로 참조하지 않겠다고 하는 것이므로, 자바스크립트 엔진이 변수의 메모리 공간에 대해 가비지 콜렉션을 수행함얕은 비교(Shallow Compare)숫자, 문자 등 원시 자료형은 값으로 비교배열, 객체 등 참조 자료형은 참조되는 위치를 비교IdentifiersuserName seasons isFinished enemies userCall StackAddress Value 0012ASF “Hass” 3241AF 5 416UHDI true 1235JFT HHSYDW1462 JFFI12HA KHS18205JAHeapAddress Value HHSYDW1462 ["ene1", "ene2", "ene3"] KHS18205JA {name: "hass", profession: "Drug dealer"}깊은 비교(Deep Compare)객체의 경우에도 값으로 비교Object Depth가 깊은 경우 : lodash 라이브러리의 isEqual() 사용Obejct Depth가 깊지 않은 경우: JSON.stringfy() 사용const obj1 = { a: 'a', b: 'b' }; const obj2 = { a: 'a', b: 'b' }; console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true 얕은 복사, 얕은 동결얕은 복사 : 내부에 중첩된 값은 복사되지 않음(기존과 동일한 객체를 참조함, 하나를 변경하면 똑같이 변경됨)얕은 동결 : Object.freeze() 메서드, 객체를 동결하여 변경할 수 없도록 함.얕은 복사를 하는 연산자 : 전개 연산자, Oject.assign(), Array.from(), slice()깊은 복사내부 중첩된 값까지 복사함JSON.parse(JSON.stringify(object)) 또는 중첩된 부분에 전개 연산자 사용 { …aObj, cObj: {…aObj.cObj }}lodash 라이브러리를 활용structuredClone(object) 메서드 활용함수 선언문, 함수 표현식함수 표현식 : 함수를 만들고 변수에 할당, 익명 함수호이스팅 시, 함수 선언식만 영향을 받음함수 표현식은 인터프리터가 해당 코드 줄에 도달할 때만 로드됨함수 표현식은 정의된 로컬 변수의 복사본을 유지할 수 있도록 호이스팅 되지 않음즉시 호출(실행) 함수 표현식(IIFE(Immediately Invoked Function Expression))정의 하자마자 즉시 실행되는 함수를 의미함기본 형태 ( function () {} )()첫 번째 소괄호( ... ) : 전역 선언을 막고, IIFE 내부로 다른 변수의 접근을 막음두 번째 소괄호 …() : 즉시 실행 함수를 생성하는 괄호, 이를 통해 Javascript 엔진이 함수를 즉시 해석하고 실행함IIFE를 변수에 할당하면 IIFE 자체는 저장되지 않고, 함수가 실행된 결과만 저장할 수 있음IIFE로 Closure가 가능하게 만든 후, 내부 함수에서 외부 함수에 있는 변수에 접근되게 함사용 목적변수를 전역으로 선언하는 것을 피하기 위해IIFE 내부에 다른 변수들이 접근하는 것을 막기 위해익명 함수를 만들기 위해 (이름이 없기 위한 두 가지 조건 중 한 가지)이 함수를 할당 받을 변수를 지정해야 함이 함수를 즉시 호출해야 함// 이름은 선택 사항이라고 했지만 function 키워드로 만들 때 에러 발생 // function (a, b) { // Error: Identifier expected. // return a- b; // } // 조건1 const minus = function (a, b) { return a - b; }; // 조건2 (function (a, b) { return a - b; })(1, 2); IIFE 적용 전const score = () => { let count = 0; return { current: () => { return count; }, increment: () => { count++; }, reset: () => { count = 0; }, }; }; console.log(score); // () => {...} 함수 출력 console.log(score().current()); // 0 score.increment(); // count를 1 증가 시킴 console.log(score().current()); // 0 => score()를 다시 실행 시켜서 count가 0으로 초기화됐기 때문에 값이 증가하지 않음 IIFE 적용 후const score = (() => { let count = 0; return { current: () => { return count; }, increment: () => { count++; }, reset: () => { count = 0; }, }; })(); console.log(score); // { current: f, ... } console.log(score.current()); // 0 score.increment(); // count를 1 증가 시킴 console.log(score.current()); // 1 Intersection observer무한 스크롤, 이미지 lazy loading 등을 구현할 때 사용됨Lazy Loading 예제<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Intersection Observer</title> <style> img { width: 400px; height: 300px; display: block; margin: 10px auto; } </style> </head> <body> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <script> const observer = new IntersectionObserver( function (entries, observer) { console.log('entries', entries); entries.forEach((entry) => { if (entry.isIntersecting) { // 루트 요소와 타겟 요소가 교차하면 console.log('옵저버'); entry.target.src = entry.target.dataset.src; // dataset에 있는 url을 src에 넣어줌 observer.unobserve(entry.target); // Lazy loading 후에는 observer 해제 } }); }, { threshold: 1 } // 타겟 요소가 루트 요소와 100% 겹치면 콜백 함수 실행 ); const imgEls = document.querySelectorAll('img'); console.log(imgEls); imgEls.forEach((img) => { observer.observe(img); }); </script> </body> </html> 순수 함수(Pure Function)함수형 프로그래밍 패러다임의 한 부분특정 함수가 다른 함수에 미치는 예기치 못한 영향을 최소화하고, 함수를 만들고 실행할 때 어떤 결과값을 리턴할지 예측할 수 있다는 장점이 있음가능한 순수 함수를 사용하는 것이 좋음클린 코드를 위해서테스트를 쉽게 하기 위해서디버그를 쉽게하기 위해서독립적인 코드를 위해서두 가지 규칙이 있음같은 입력이 주어졌을 때, 언제나 같은 결과값을 리턴한다 (same input → same output)사이드 이펙트를 만들지 않는다 (동일한 input이지만 외부 값에 의해 변경되는 output → impure하게 만듦)커링(Curry Function)함수와 함께 사용하는 고급 기술 (다른 언어에도 존재하는 기술)f(a, b, c) 처럼 단일 호출로 처리하는 함수를 → f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합될 수 있게 변환하는 것즉, 함수를 호출하는 것이 아닌 변환하는 것예제const sum = (x, y) => x + y; console.log(sum(10, 20)); // 30 const curriedSum = (x) => (y) => x + y; console.log(curriedSum(10)); // f() y => x + y console.log(curriedSum(10)(20)); // 30 const makeFood = (ingredient1) => { return (ingredient2) => { return (ingredient3) => { return `${ingredient1} ${ingredient2} ${ingredient3}`; }; }; }; // const hamburger = makeFood('Bread')('Ham'); // console.log(hamburger); // (ingredient3) => {...} 완성되지 않음 const hamburger = makeFood('Bread')('Ham')('Tomato'); console.log(hamburger); // Bread Ham Tomato => 3개의 인수를 다 넣어줘야 완성됨 // makeFood 축약 const cleanMakeFood = (ingredient1) => (ingredient2) => (ingredient3) => `${ingredient1} ${ingredient2} ${ingredient3}`; const newHamburger = cleanMakeFood('Bread')('Ham')('Tomato'); console.log(newHamburger); // Bread Ham Tomato // 일반 log 함수 function log(date, importance, message) { alert(`[${date.getHours()} : ${date.getMinutes()}]: [${importance} ${message}]`); } log(new Date(), 'DEBUG', 'same bug'); // 커리 함수 function curry(f) { return function (a) { return function (b) { return function (c) { return f(a, b, c); }; }; }; } const curriedLog = curry(log); curriedLog(new Date())('DEBUG')('same bug'); // 동적으로 추가되는 커리 함수 (매개 변수 개수 상관 없이 커리 함수로 변경 가능) function curry2(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this.args); } else { return function (...args2) { return curried.apply(this, args.concat(args2)); }; } }; } // const sum = (x, y, z) => x + y + z; const sum = (x, y, z, j) => x + y + z + j; const curriedSum = curry2(sum); // console.log(curriedSum(1)(2)(3)); console.log(curriedSum(1)(2)(3)(4)); strict modeJavascript의 엄격 모드제한된 버전을 선택하여 암묵적인 “느슨한 모드(sloppy mode)”를 해제하기 위한 방법시멘틱스에 몇 가지 변경이 일어남기존에는 무시되던 에러를 throwingJavascript 엔진의 최적화 작업을 어렵게 만드는 실수를 바로 잡음. 가끔 엄격 모드의 코드가 비엄격 모드와 동일한 코드 보다 더 빠르게 작동하도록 만들어줌.적용 방법파일에 "use strict" 지시자 입력함수 안에 "use strict" 지시자 입력하여 해당 함수만 적용 가능class를 사용하면 자동으로 strict 모드 적용자바스크립트 파일의 타입을 module로 설정하면 strict 모드 적용<script src="script.js" type="module">기존과 차이점할당할 수 없는 값에 할당하려 할 때 ex) 선언되지 않은 변수, undefiend, NaN수정할 수 없는 값을 수정하려 할 때 ex) readOnly 객체동일한 이름의 파라미터가 존재할 때함수 내의 this가 undefiend가 됨

프론트엔드인프런워밍업스터디클럽FE1기발자국

짙은

[인프런 워밍업 스터디 클럽 1기] 백엔드 2주차 회고록

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 회고록입니다.Plus 😍이번 주 내내 일정이 많았음에도 과제를 성실히 해나갈 수 있었던 건, 워밍업 클럽 덕분이라 생각한다! 특히 제출 예정 기간보다 빠르게 과제를 제출하시는 분들을 보며 자극을 많이 받는다. 나의 태도에 대한 반성은 덤Minus 🥶개인적인 일정이 많아 공부 시간을 확보하기 힘들었다. 이미 강의는 수강했기 때문에 제출일에 과제만 말아서 제출하는 방식으로 진행했는데, 추가 과제는 대부분 진행하지 못해 아쉬웠다. Interesting 🤔최근에 원온원 미팅을 진행하면서, 팀장님이 공부를 할 때 모든 것을 이해하려고 하지말고 빠른 속도로 훑어나가는걸 계속해서 반복하는 방법으로 공부 방법을 바꿔보는건 어떻겠냐고 조언을 주셨다. 백엔드 개발을 위해서는 하나하나를 아는 것도 중요하지만 전체적인 흐름을 알아야 하는 부분이 크다보니 실제 개발에 사용하기 힘들다고 생각되는 부분이 있었는데, 그렇게 공부 방법을 바꾸면 도움이 될 것 같았다. 실제로 팀장님은 백엔드 개발자이기도 하고. 이번 주에는 개인 시간을 조금 더 투자해서 강의를 더 많이 들을 수 있도록 해야겠다.

백엔드인프런워밍업스터디클럽

임하스

[인프런 워밍업 스터디 클럽 1기] FE 1주차 발자국

시작공부하며 개인적으로 중요하다 생각하는 부분과 복습할 내용, 헷갈리던 부분들을 정리해보았다. 프레임워크와 라이브러리에 의존적이던 나를 되돌아보는 시간이었던 것 같다.  정리Console 객체console.log(...data: any[]): void 로그console.error(...data: any[]): void 에러console.warn(...data: any[]): void 경고console.table(tabularData?: any, properties?: string[]): void 객체 데이터를 테이블로 표기console.time(label?: string): void, console.timeEnd(label?: string): void 사이에 코드를 입력하여 속도 측정 가능. label을 넣어서 무엇에 대한 측정인지 설명을 달 수 있음console.count(label?: string): void, console.countReset(label?: string): void 호출된 횟수 반환(특정 데이터가 몇 번 호출됐는지) 변수의 참조 범위(scope)자바스크립트는 블록 레벨 스코프 호이스팅var, let, const 셋 다 호이스팅 됨근데 var는 선언과 undefined로 할당되기 때문에 참조가 가능하고, let과 const는 선언만 되고 할당되지 않음 자바스크립트 타입원시타입은 콜스택에 저장(불변성을 가짐)참조 타입 값은 힙에 저장값에 대한 주소는 콜스택에 저장원시타입 : Boolean, String, Number, null, undefined, Symbol참조타입 : Object, Array, functuon, classes, …자바스크립트는 동적 타입이다어떤 특정 타입과 연결되지 않으며, 모든 타입의 값으로 할당(및 재할당) 가능배열(Array)은 typeof를 쓰면 obejct로 나오기 때문에, Array.isArray(변수) 메서드를 통해 배열인지 확인할 수 있다배열이 object 타입이라고 나오는 것이 잘못된 것이 맞는데, 공식에서도 이것을 인지하고 있지만 당시에 이미 object로 처리하여 개발한 곳들이 너무 많아서, 혼란을 일으키지 않기 위해 그대로 object로 두기로 했다고 한다 자바스크립트 타입 변환String(변수), 변수.toString() 차이Number(변수), parseInt(변수) 차이자바스크립트가 자동으로 변수 타입을 변경하는 기준(ex. string 타입 + number 타입 ⇒ string 타입) Loopsfor … in : 객체의 속성을 따라 반복 ⇒ 객체 순회객체의 열거 가능한 속성들을 반복할 때 사용됩니다.객체의 속성들을 순회하며 각 속성의 이름(키)을 반환합니다.일반적으로 객체의 속성을 순회할 때 사용되며, 배열을 순회할 때는 사용하지 않는 것이 좋습니다.for … of : 반복 가능한 객체를 반복 ⇒ 배열 순회반복 가능한(iterable) 객체(배열, 문자열, 맵, 셋 등)의 요소들을 반복할 때 사용됩니다.각 요소의 값을 반환합니다.객체의 속성을 직접 열거하지 않고 요소들을 순회할 때 사용됩니다.forEach() : void 타입, for 루프는 await과 함께 작동하지만 얜 아님.map() : return 필요for 루프가 forEach보다 빠름 Window 객체**window 객체**브라우저에서 제공하는 객체브라우저의 창(window)을 나타냄. 브라우저 창에 대한 정보를 알 수 있고, 제어도 가능var 키워드를 통해 변수를 선언하면 window 객체의 프로퍼티가 됨let과 const는 window 객체 내부의 블록에서 선언된 것 → 전역 객체의 프로퍼티로 활용될 수 없음DOM(Document Object Model)메모리에 웹 페이지 문서 구조를 트리 구조로 표현해서 웹 브라우저가 HTML 페이지를 인식하게 해주는 객체 모델window.documentBOM(Browser Object Model)웹 브라우저와 상호작용하는 데 사용되는 객체 모델window.location, window.history, window.navigator 등속성window.innerWidth 속성브라우저 창의 내부 너비컨텐트 영역의 너비콘솔창/스크롤 바가 있는 경우에는 이를 제외한 영역의 너비window.outerWidth 속성브라우저 창의 외부 너비창의 테두리를 포함한 전체 외부 너비**window.scrollX 속성**x축 scroll의 시작점 좌표**window.scrollY 속성**y축 scroll의 시작점 좌표**window.location 속성**현재 창의 URL을 나타내는 객체**window.history 속성**브라우저의 세션 기록을 조작하고 탐색할 수 있는 객체window.location.href vs window.history.push()일반적인 이동은 history.push(), 이동과 함께 새로고침이 필요한 경우는 window.location.href 추천공통점 : 페이지 이동 location.href history.push() HTTP 요청 수행 O X 새로고침 O X Applcation 상태 유지(ex. 리액트의 state) X O 용도 현재 페이지의 URL 제어 브라우저의 탐색 히스토리 조작**window.navigator 속성**브라우저의 정보와 기능을 제공하는 객체주로 브라우저의 종류, 버전, 운영 체제, 언어 설정 등을 확인하고, 이에 따라 웹 애플리케이션을 조건부로 실행하는 데 사용함웹 페이지 빌드 과정(CRP, Critical Rendering Path)HTML → DOM TreeCSS → CSSOM Tree1, 2렌더 엔진이 문서를 읽어들여 파싱어떤 내용을 페이지에 렌더링할 지 결정비용이 크지 않은 작업DOM Tree + CSSOM 결합 → Render Tree브라우저가 DOM과 CSSOM을 결합화면에 보이는 모든 콘텐츠와 스타일 정보를 모두 포함하는 최종 렌더링 트리 출력Layout(reflow)브라우저가 페이지에 표시되는 각 요소의 크기와 위치를 계산Paint실제 화면에 그리기 DOM노드 타입Element, Attribute(deprecated), Text node, Comment node, Document itself, Doctype모든 타입의 노드에 적용 가능한 탐색 프로퍼티parentNode, childNodes, firstChild, lastChild, previousSibling, nextSibling요소 노드(Element 타입)에만 적용 가능한 탐색 프로퍼티parentElement, children, firstElementChild, lastElementChild, previousElementSibling, nextElementSibling자식 노드바로 아래의 자식 요소후손 노드중첩 관계에 있는 모든 요소자식 노드와 그 아래 자식 노드 모두 포함**element.innerHTML**html까지 출력됨**element.innerText****innerText**는 해당 요소의 텍스트 콘텐츠만을 반환HTML 태그를 고려하여 렌더링된 텍스트를 반환함(공백을 하나로 처리)CSS로 display: none; 또는 **visibility: hidden;**으로 숨겨진 요소의 텍스트는 반환하지 않음**innerText**는 보다 시각적으로 화면에 나타나는 텍스트만을 반환하며, 렌더링된 결과를 고려하여 처리됨**element.textContent****textContent**는 해당 요소의 모든 텍스트 콘텐츠를 포함하여 모든 하위 요소의 텍스트도 포함하여 반환HTML 태그를 무시하고 텍스트만을 고려함. 즉, HTML 구조를 고려하지 않고 텍스트만을 처리HTML 태그의 텍스트도 그대로 반환되며, 스크립트에서 태그를 제거할 필요가 없음**textContent**는 보다 일반적인 텍스트 콘텐츠를 반환하며, 숨겨진 요소의 텍스트도 포함될 수 있음**element.childNodes**text 노드가 포함된 NodeList 반환text 노드 : line break(엔터, 한 칸 띄움) 포함하여 반환배열 같아 보이지만, 유사 배열 객체인 컬렉션 타입컬렉션 타입 특징배열에서 사용하는 메서드를 사용할 수 없음배열의 반복 for … of, forEach() 사용 가능 (for … in 불가능)**element.children**children element nodes 반환text 노드도 포함 DOM 제어parentNode.appendChild(node) : node 추가parentNode.removeChild(node) : node 삭제parentNode.replaceChild(newNode, oldNode) : oldNode를 newNode로 대체 Event Listener & Event 객체이벤트를 등록하는 방법자바스크립트 코드에서 프로퍼티로 등록 (ex. onload)HTML 태그에 속성으로 등록 (ex. onClick)addEventListener 메서드 활용element.addEventListener(이벤트명, 실행할 함수명, 옵션)Event 객체event.clientY : 브라우저로부터의 마우스 이벤트가 발생한 거리 Y 좌표event.offsetY : 요소로부터의 마우스 이벤트가 발생한 거리 Y 좌표event.target : 실제 이벤트가 시작된 타겟 요소this 또는 event.currentTarget : 핸들러가 할당된 요소 Event 종류UI 이벤트load문서나 객체가 로드 완료 되었을 때 발생change객체의 내용이 변동되거나 focus를 잃었을 때 발생resize객체의 크기가 바뀌었을 때 발생scroll스크롤바를 조작할 때 발생error에러가 발생했을 때 발생키보드 이벤트keydown키를 눌렀을 때 발생keyup키를 눌렀다 뗐을 때 발생keypress사용자가 누른 키의 문자가 입력되었을 때 발생마우스 이벤트click객체를 클릭했을 때 발생dblclick객체를 더블클릭했을 때 발생mousedown마우스를 클릭했을 때 발생mouseout 마우스가 요소 밖으로 나갔을 때 발생 (자식 요소에 대해서도 이벤트를 처리함)mouseleave마우스가 요소 밖으로 나갔을 때 발생 (자식 요소에 대해서는 이벤트를 처리하지 않음)mouseover 마우스가 요소 위로 올려졌을 때 발생mousemove마우스가 움직였을 때 발생mouseup마우스에서 손을 뗐을 때 발생포커스 이벤트focus객체에 focus가 되었을 때 발생blur객체가 focus를 잃었을 때 발생폼 이벤트inputinput, textarea 요소 값이 변경되었을 때 발생changeselect box, check box, radio button의 상태가 변경되었을 때 발생select텍스트를 선택했을 때 발생reset리셋 버튼을 눌렀을 때 발생submit사용자가 버튼키 등을 활용해 폼을 전송할 때 발생cut/copy/paste사용자가 폼 필드의 콘텐츠를 잘라내기/복사/붙여넣기했을 때 발생동일한 요소에 이벤트가 적용되어 있을 때, 이벤트 발생 순서keydown → keypress → keyupmousedown → mouseup → click이벤트의 3단계 흐름 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계버블링 단계 : 이벤트가 상위 요소로 전파되는 단계 Event Bubbling이벤트 버블링이란, 가장 깊게 중첩된 요소에 이벤트가 발생했을 때 이벤트가 위로(bubble up) 전달 되는 것을 의미함타깃 이벤트에서 시작해서 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생3번 요소, 2번 요소, 1번 요소에 이벤트 핸들러가 있다면, 3번 요소 핸들러 → 2번 요소 핸들러 → 1번 요소 핸들러 순으로 실행됨event.stopPropagation() 메서드를 활용하여 버블링을 중단하면 부모 요소(상위 요소)의 핸들러는 호출되지 않음 Event Capturing이벤트 캡처링이란, 제일 상단에 있는 요소에서 아래로 이벤트가 내려오는 것을 의미함addEventListener() 메서드의 두 번째 인수에 { capture : true } 옵션을 넣어주면 캡처링 과정을 확인할 수 있음 이벤트 위임 (Event Delegation)하위 요소의 이벤트를 상위 요소에 위임하는 것을 의미. 즉, 하위 요소의 이벤트를 상위에서 제어함동일한 기능을 가진 요소임에도, 추후에 코드를 통해 추가된 요소일 경우 이벤트 리스너를 다시 등록해줘야 하는 번거로움이 있음 → 이벤트 위임을 활용하여 해결할 수 있음하지만 주의할 점이 한 가지 있음. 상위 요소에 등록된 이벤트 핸들러가 하위 요소에 있는 요소에 대해서만 동작하도록 설정해야함. 이렇게 하지 않으면 상위 요소의 아무 곳이나 클릭했을 때도 이벤트가 발생할 수 있음하위 요소가 버튼이라고 쳤을 때, 이벤트 핸들러에서 클릭된 요소가 버튼인지 확인하는 로직이 필요함  회고역시 기본기가 가장 중요하다는 것을 다시 느꼈다. React를 쓰며 state로 인해 자동으로 업데이트 되는 화면에 익숙해졌던 것 같다. 프레임워크와 라이브러리에 대한 의존 없이도 스스로 구현해낼 수 있도록 연습하는 것이 좋겠다.  

프론트엔드인프런워밍업스터디클럽FE1기발자국

짙은

[인프런 워밍업 스터디 클럽 1기] 백엔드 1주차 회고록

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 회고록입니다.   Plus 😍 정해진 진도표를 참고해 내 일정에 맞게 꾸준히 강의를 듣는건 오랜만인데 생각보다 더 좋다. 특히 강의의 구조가 그저 진도를 나가는데만 집중하는게 아니라 서론(문제 제기), 이론(제기된 문제의 해결), 결론(해당 개념이 나오게 된 이유와 어떻게 해결했는지에 대한 요약 정리)으로 이루어져 있어, 무턱대고 하루 n강을 수강하는 것보다 진도표를 참고해 유사한 내용을 하루 단위로 묶어서 들을 수 있어서 좋았다. 특히 이런 구조가 좋았던데는 개인적인 배경이 있는데...... 현재 나는 프론트엔드 개발자로 입사했으나, 회사의 방침(?)이 바뀌어 풀스택 개발자로 (반강제) 커리어 전환을 해야만 하는 환경에 있고, 백엔드 역량을 단시간내에 끌어올려야만 하는 상황이다. 이미 작성된 코드들을 참고해 CRUD를 (굉장히 천천히) 만들어낼수는 있지만, 왜 이런 구조로 나뉘어져 있는지는 코드를 통해서 알기 힘들었다. 다른 개발자들도 동일한 여건에 처해 있기 때문에 이런 구조에 대한 궁금증을 선뜻 물어보기가 쉽지 않은 환경인데, 강의에서 이런 부분들을 상세히 알려주어 도움이 많이 되었다. 특히 18강 Controller를 3단 분리하는 강의를 들으며 Layered Architecture에 대해 공부할 때 막연하게 '세 단계로 나누어서 코드를 작업하네'라고 생각했던 부분에 대해 명확한 이유를 알게 되어, 앞으로 백엔드 코드를 읽거나 작성할 때 도움이 많이 될 것 같다.   Minus 🥶 생각보다 내가 자바를 잘 모른다! 자바 책 또는 강의도 병행하면 좋겠지만, 사실 현재 진도표에 맞춰 강의를 듣는 것도 빡빡하다. 실습이 대부분이다보니 출퇴근 시간 지하철에서 강의를 듣는다거나 하는 식으로 병행하기 힘들다는 한계도 있다. 강의를 들으며 모르는 내용이 나올 때마다 검색으로 부족한 부분을 채워나가는 방식으로 (일단은) 메꾸어야 할 것 같다. 어떤 것들을 공부했었는지 키워드를 따로 정리했다가 백엔드 스터디가 종료되고 자바 기초 공부를 시작할 때 그 키워드들을 예의주시하며 공부하는게 최선일 것 같다. 회사의 개발 환경은 맥북인데, 집에서는 윈도우 PC를 이용하고 있다. 인텔리제이 키 매핑이 다르다보니 한번씩 혼란스럽다.   Interesting 🤔현재 DB에 적재된 데이터를 외부 팀의 요청에 맞게 추출하거나, 수정해야 할 때가 있는데 이런 요청과 쿼리를 정리해뒀다가 어드민 페이지에 추가 기능으로 구현하면 어떨까? 하는 생각이 들었다. 실제로 이전에도 반복되는 요청에 대해 추가 기능을 만들면 좋을 것 같다고 백엔드 개발자분께 건의한 적이 있지만 부정적인 답변을 받았었던 경험이 있다. 그렇지만 내가 프론트엔드부터 백엔드까지 모두 개발한다면 아무런 문제가 없지 않을까.   

백엔드인프런워밍업스터디클럽

miiro

[인프런 워밍업 스터디 클럽 1기] BE 1주차 회고록

첫 번째 발자국자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 수강하고인프런 워밍업 클럽에 참여하여 쓰는 첫 번째 회고록입니다. 학습 내용스프링 프로젝트를 시작하는 방법과 네트워크, HTTP, API에 대한 기본적인 개념에 대해 학습하였습니다.HTTP는 데이터를 주고 받는 표준이고, 행위와 자원은 HTTP 요청을 보내기 전에 약속을 해야합니다.HTTP Method는 GET, POST, PUT, DELETE 가 있습니다. 예시로 GET는 HTTP 요청을 받는 컴퓨터에게 요청하는 행위입니다.GET 요청의 경우 /portion 이라는 요청 받는 path와 ?의 구분기호로 세부 조건인 쿼리스트링 작성합니다.GET /portion?color=red&count=2POST 요청의 경우 데이터를 저장하는 것으로 바디를 사용합니다.POST /oak/leatherDELETE는 데이터를 삭제하기 위해 쿼리스트링을 사용하고, PUT은 데이터를 수정하기 위해 바디를 사용합니다. POST API는 HTTP Body를 활용해서 데이터를 받습니다.데이터를 보낼 때는 JSON(객체표기법)을 활용합니다.key : 'value' 형태로 Java 문법 상 Map<Object, Object> 형태와 유사합니다.@PostMapping("/multiply") public int multiplyTwoNumbers(@RequestBody CalculatorMultiplyRequest request){ return request.getNumber1()* request.getNumber2(); }위의 예시의 주요 어노테이션은 아래와 같습니다.@PostMapping : HTTP Method가 POST 이고, 요청 경로인 /path인 API를 생성합니다.@RequestBody : HTTP Body로 들어오는 JSON 파라미터로 데이터를 전달하는 객체인 DTO로 바꿔줍니다. DTO에는 JSON의 key 값이 명시되어야하며, 각 속성은 key 값과 동일하게 value도 타입에 맞게 작성합니다. Database는 데이터를 구조화시켜 저장합니다. 마치 엑셀과 비슷하게 표처럼 구조화하여 저장합니다.여기서 구조화된 데이터를 조회하는 언어를 SQL 이라고 합니다.SQL문의 예시는 아래와 같습니다.// DB 생성 create database [데이터베이스 이름]; // DB 조회 show databases; // DB 삭제 drop database [데이터베이스 이름]; // DB 사용 use [데이터베이스 이름]; // TABLE 조회 show tables; // TABLE 생성 create table [table 이름] ([필드 이름] [타입] [부가 조건], [필드 이름] [타입] [부가 조건], ... primary key([필드 이름]) ); // TABLE 삭제 drop table [table 이름];테이블 자체를 생성, 삭제, 변경, 초기화 하는 것을 DDL(Data Definition Language) 라고 합니다. 테이블의 데이터를 조작하는 것을 일명 C.R.U.D 라고 부릅니다.테이블 데이터를 조작하는 SQL문을 DML(Data Manipulation Language) 라고 합니다.데이터 삽입insert into [table 이름]([필드 이름1], [필드 이름2], ...) values (값1, 값2, ...);데이터 조회select * from [db 이름]; select [필드 이름1], [필드 이름2] from [db 이름];조건 데이터 조회select * from [db 이름] where [조건];데이터 업데이트update [table 이름] set 필드1=값, 필드2=값,... where [조건];❗ 조건문을 작성하지 않으면 모든 데이터가 바뀌니 항상 주의하자!데이터 삭제delete from [table 이름] where [조건];❗ 조건문을 작성하지 않으면 모든 데이터가 삭제되니 항상 주의하자!  과제 내용Day 1HTTP 요청을 보내기 위한 Method 중 하나인 GET API 를 만들기 위해서 어노테이션을 사용했었습니다.이를 통해 어노테이션을 사용하는 이유는 무엇인지, 또한 나만의 어노테이션을 만드는 방법에 대해 학습하게 되었습니다.처음에는 클래스 위에 붙은 @ 어노테이션은 의미를 생각하기보다는 레이어에 맞는 동작을 하기 위해서 알맞는 어노테이션을 사용할 수 있도록 암기하는 것으로만 생각하고 공부했었습니다. 자바에서 제공하는 표준 어노테이션을 다양하게 찾아봤으며, 코드가 작동되면서 reflection을 이용하여 코드를 직접적으로 호출하지 않고 코드를 제어할 수 있는 다양한 기술의 집합체라는 것을 알게 되었습니다.그리고, 유저가 직접 만드는 어노테이션을 구현해보면서 메타 어노테이션 즉, 내가 만드는 어노테이션의 유지 기간, 위치를 결정할 수 있도록 결정할 수 있는 사용자 친화적인 조작을 할 수 있다는 것을 알게 되었습니다.📋 1일차 미션 : 어노테이션의 개념과 특징 Day 2GET, POST API를 활용한 과제인데 일반적으로, GET을 사용할 때는 URL 뒤에 query를 사용하는 방식, POST를 사용할 때는 body를 사용하는 방식으로 볼 수 있습니다. 하지만 부득이하게 데이터의 양이 많이 복잡하다면 쿼리 파라미터로 받는 방식(GET)보다는 body 로 받는 방식(POST)로 변경하는 방향이 좋다는 것을 알았습니다.또한, LocalDate 타입을 string 형태로 받아 치환을 해줬는데, 스프링부트 2.x.x 버전에서 @DateTimeFormat을 활용하여 localDate를 바로 받을 수 있는 방법도 알게 되어, java 문법의 변수가 아닌 스프링에 친화적인 어노테이션을 활용하여 구현할 수 있도록 많이 접해보고 공부해야겠다는 생각이 들었습니다.📋 2일차 미션 : GET API와 POST API Day 3람다식의 등장한 이유와 람다식과 익명 클래스의 관계에 대한 내용을 중점적으로 공부하는 것이었습니다.위의 내용을 공부하면서 함수형 프로그래밍과 @FunctionalInterface, stream API와 메서드 레퍼런스에 대한 내용에 대한 연관성도 함께 예시를 만들어보면서 공부했습니다. 위의 내용은 코딩테스트를 해본 사람이면 한 번 쯤은 실습해봤을 내용이라서 쉽게 내용을 이해할 수 있을 것이라고 생각했습니다.하지만 필자의 경우 문법에 대한 내용보다는 코드를 구현할 줄만 알았습니다. 해당 내용을 공부하니 왜 만들어졌는지, 자바에서는 버전을 올라가면서 메서드를 쉽게 구현할 수 있도록 점차 단순화시키고 간결하게 표현할 수 있다는 것에 대해 면밀하게 알 수 있었습니다.📋 3일차 미션 : 익명 클래스와 람다식  회고기초 지식보다는 실제 프로젝트를 구현하기 위한 기술에 대한 내용에 중점을 두고 공부를 했었는데, 백엔드 개발을 하기 위한 서버 지식과 Java, Spring에 관한 내용을 다시 처음부터 하나씩 이해하면서 다시금 지식을 쌓을 수 있었던 거 같습니다.또한, 강의만 듣는 게 아닌 과제를 해결하면서 강의에 대한 이해와 구현할 수 있는 능력을 키우면서 나 자신의 부족한 점과 다른 방식으로 구현할 수 있을 수 있을까라는 여러 방법에 대해 골똘하게 생각할 수 있는 계기가 되었던 거 같습니다.

백엔드인프런워밍업스터디클럽1주차회고록

채널톡 아이콘