스터디 1회차
스터디 1회차를 진행했다.
전에 공부했던 부분을 발표했고 추가로 CRUD를 aws에 연동한 데이터베이스로 진행해 보기로 해서 하고 있었지만 완성을 하진 못 했다. 이어서 완성해야 할 것 같다.
스터디에 나온 쌤들 모두 각자 열심히 준비해 온 것을 발표했다. 발표를 하면서 배우고 발표를 들으면서 배웠다. 4시간을 진행했는데 짧게 느껴졌다.
오늘이 첫 스터디였지만 쌤 한 분이 코로나 밀접 접촉자라고 하셔서 못 나오셨다. 다음 주에도 못 나오신다고 하셨다. 세 명에서 진행했지만 알차게 잘 진행한 것 같아 뿌듯하다.
몇 가지 스터디 규칙도 변경됐다.
우선, 발표 시간이 생각보다 많이 잡아먹었고 생각보다 더 도움이 됐다. 그래서 이것을 좀 줄이기 위해 한 쌤이 의견을 제시했는데 발표 자료를 사전에 노션에 공유하고 미리 보고 질문만 받자는 것 이였다. 오케 ! 좋다. 이러면 발표 시간이 줄어들 것 같다. 근데 아직 내 실력엔 질문을 바로 답변 못 할 수가 있을 것 같아 추가로 혹시 사전에 질문도 노션 댓글에 남겨주시면 좋을 것 같다는 의견을 냈고 역시 받아들여져서 다음 회차 부터는 사전에 공유 -> 각자 팀 발표 리딩 -> 질문 사항 댓글 -> 스터디 당일 질문 위주로 발표 이렇게 진행 될 것 같다. 매우 좋은 지식 순환 구조를 가진 스터디가 될 것 같다는 예상이다. 실제로 오늘도 쌤들에게 여러 가지를 들었고 매우 도움이 되었다.
발표와 리뷰 자료에는 사진도 많이 들어갔지만 블로그에 업로드 할 때에는 사진은 빼고 올릴 예정이다.
노션으로 가면 자세하게 나와 있다.
발표
머신러닝 & 데이터
- 앞으로 머신러닝을 주도하는 회사가 시대를 이끌게 될 것이다.
- 머신러닝은 결국 데이터를 어떻게 활용하나로 귀결된다.
- 데이터를 사람이 본다. → 시각화 필요 → 사람이 수 많은 데이터를 분류하며 보기가 어렵다. → 기계로 대체한다. → 기계가 개인화를 한다. ⇒ 이것이 머신러닝, 인공지능이다.
- ML Ops
- 데이터는 무한하게 반복되어야 한다.
- 쇼핑몰에서 쇼핑을 할 때 개인화를 통해 구매 할 확률이 높은 제품을 추천한다. → 구매하지 않는다. → 구매하지 않는 부분 또한 데이터화한다. ⇒ 데이터는 데이터를 만들어 무한히 반복된다.
- 데이터는 무한하게 반복되어야 한다.
- ML Platform
- 클라우드 서비스를 사용하기 위해 직접 개발하지 않고 AWS를 사용하는 것 처럼, 머신러닝을 사용하기 위해 직접 개발하지 않고 ML Platform을 사용하게 될 것이다.
- ML을 직접 개발하는 것은 엄청난 인재들이 필요하기 때문에 작은 회사에서 이를 한다는 게 사실상 어려우므로 우리는 ML Platform을 어떻게 잘 활용할 수 있을지 고민하고 공부하면 좋을 것이다.
- 행동 데이터? 스냅샷 데이터?
- 스냅샷 데이터는 간단하게 그 사람 자체의 데이터이고 행동 데이터는 이 사람의 의사결정이 들어간 데이터다.
- 스냅샷 데이터 보다는 행동 데이터가 중요하고 이런 데이터를 잘 가공해 질 높은 데이터를 갖추는 것이 앞으로 머신러닝의 시대의 경쟁력이 될 것이다.
- 스냅샷 데이터는 간단하게 그 사람 자체의 데이터이고 행동 데이터는 이 사람의 의사결정이 들어간 데이터다.
스프링 부트 : DIP(의존 관계 역전 원칙)
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- MemberServiceImpl을 보면 private final MemberRepository memberRepository = new MemoryMemberRepository(); 를 통해 추상화와 구현체를 관리하고 있다.
- 만약 요구사항 변경으로 새로운 구현체로 변경해 줘야 한다면 = new MemoryMemberRepository(); 이 부분에 대한 수정이 필요해진다.
- MemberServiceImpl에서 추상화를 통해 구현체를 관리하고 구현체에 대한 변경이 있을 때 인터페이스를 통해서만 구현체를 변경할 수 있어야 하는데 지금 보면 인터페이스가 아닌 MemberServiceImpl에서 코드 수정이 일어난다.
- 결국 지금 이 코드는 객체 지향 5가지 원칙 중 DIP*(Dependency Inversion Principle | 의존 관계 역전 원칙)*에 위반된다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
- 해결을 위해 AppConfig 클래스를 만들어 이런 구현체들을 관리해 준다.
- 생성과 연결을 담당한다.
- return new MemberServiceImpl(new MemoryMemberRepository()); 코드로 구현 객체를 생성한다.
- 간단히 해석하자면 “MemberServiceImpl을 만들고 내가 만든 MemberServiceImpl은 MemoryMemberRepository를 사용할거야.!” 와 같이 해석할 수 있다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- public MemberServiceImpl(MemberRepository memberRepository) { this.memberRepository = memberRepository; } 생성자를 통해 받아준다.
- 이렇게 따로 설정 클래스에서 구현체를 받아 생성자를 통해 추상화와 구체화를 분리해 주는 것을 생성자 주입이라고 한다.
- AppConfig 클래스에서는 결국 실제 동작에 필요한 구현 객체를 생성하게 된다.
- AppConfig 클래스는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입(연결)해준다.
- 결국 AppConfig 객체는 객체를 생성하고 생성한 객체를 생성자로 전달하는 행위를 하게 되고 이런 행위를 DI(Dependency Injection)라고 한다.( 의존관계 주입)
- 결과적으로는 MemberServiceImpl 클래스는 인터페이스에만 의존하게 되고 구체적인 구현 클래스에 대해 전혀 의존하지 않게 되므로 DIP를 잘 지키는 코드가 된다.
MemberService memberService;
@BeforeEach
public void beforEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
//MemberService memberService = new MemberServiceImpl(memberRepository);
- @BeforeEach 는 각 테스트를 실행하기 전에 무조건 실행시켜준다.
- 마지막으로 테스트 코드 또한 바꿔주고 테스트 전체 실행을 해주면
- 성공적으로 DIP 보안을 마치게 된다.
코드 리팩터링
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
- 이 코드는 객체 안에서 new를 통해 특정 객체를 직접 생성하고 있다.
- 이러면 문제가 중복이 생긴다.
- 예를 들면 MemberServiceImpl과 OrderServiceImpl에서MemoryMemberRepository를 사용하기 위해 각각 위 코드처럼 (new MemoryMemberRepository()); 이렇게 따로 생성해 줘야 하기 때문에 이 자체를 중복이라고 본다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
private MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
- 리팩터링 후에는 memberRepository()에 new MemoryMemberRepository()를 생성하고 MemberServiceImpl과 코드엔 없지만 OrderServiceImpl에서 따로 new를 통해 생성하지 않고 memberRepository()를 통해 객체를 생성한 것을 불러와 사용하게 되어 전체적으로 객체 생성 행위는 줄어든 것이므로 중복을 피했다보고 이러면 DIP에 이어 OCP도 만족하게 된다.
- DIP / OCP 참고
- DIP
- 의존 관계 역전 원칙
- 구체적인 것이 추상화에 의존해야 한다.
- 고차원 모듈은 저차원 모듈에 의존하면 안 되고 추상화된 것에 의존해야 한다.
- 의존 관계 역전 원칙
- OCP
- 개방 - 폐쇄 원칙
- 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다.
- 확장에 대해 열려 있어야 하고 수정에 대해 닫혀 있어야 한다.
- 개방 - 폐쇄 원칙
- DIP
리뷰
aws ec2 Ubuntu Mysql
- mysql 포트 번호 : 3306을 인바운드 규칙에 추가한다.
- aws에서 발급 받은 .pem 키를 .ppk로 바꿔 PuTTY로 접속한다.
- MySQL을 설치하고 외부 접속 허용 설정도 한다.
- MySQL Workbench에서 호스트와 포트를 넣고 연결한다.
- 스키마 생성과 테이블 생성을 해주고 컬럼을 생성해 준다.
- jdbc 드라이버를 통해 aws와 연결한다.
- ( 정상 작동하는지 확인은 아직 못 함 )
- 테스트 코드를 만들어보려 했으나 junit 라이브러리가 없다.
- 구글링으로 찾아서 추가해 줬다.
- 문제 : @Test 어노테이션이 안 된다?,,
암호화 로직 추가
package poly.util;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
public class EncryptUtil {
/*
* 암호화 알고리즘에 추가시킬 암호화 문구
*
* 일반적인 암호화 알고리즘 SHA-256을 통해서만 암호화 시킬 경우, 암호화 된 값만 보고 일반적인 비밀번호에 대한 값을 쉽게 예측이
* 가능함 따라서, 암호화 할 때 암호화되는 값에 추가적인 문자열을 붙여서 함께 암호화를 진행한다.
*/
final static String addMessage = "ohjuhyeon";
/*
*--------------------------------------------------------------------------------------------
* AES128-CBC 암호화 알고리즘에 사용되는 초기 백터와 암호화 키
*/
// 초기 백터 (16Byte 크기를 가지며, 16Byte 단위로 암호화 시, 암호화 할 총 길이가 16Byte가 되지 못 하면 뒤에 추가하는 바이트)
final static byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
//AES128-CBC 암호화 알고리즘에 사용되는 키 (16자리 문자만 가능하다.)
final static String key = "Ohjuhyeon1234567"; // 16글자가 필요하다. (영문 1글자당 1Byte)
//--------------------------------------------------------------------------------------------
/*
* 해시 알고리즘 ( 단방향 암호화 알고리즘 ) SHA-256
*
* @param 암호화 시킬 값
*
* @return 암호화된 값
*/
public static String encHashSHA256(String str) throws Exception {
String res = ""; // 암호화 결과값이 저장되는 변수
String plantText = addMessage + str; // 암호화 시킬 값에 보안 강화를 위해 임의 값을 추가한다.
try {
/*
* Java는 기본적으로 표준 암호화 알고리즘을 java.security 패키지를 통해 제공한다. 여러 해시 알고리즘 중 가장 많이 사용되는
* SHA-256을 지원하고 있다.
*/
MessageDigest sh = MessageDigest.getInstance("SHA-256");
sh.update(plantText.getBytes());
byte byteData[] = sh.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
res = sb.toString();
// Java에서 제공하는 알고리즘이 아닌 경우, 에러 발생
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
res = "";
}
return res;
}
//--------------------------------------------------------------------------------------------
/*
* AES128 CBC 암호화 함수
* 128은 암호화 키 길이를 의미한다.
* 128bit = 16byte (1Byte=8bit * 16 = 128)
*/
public static String encAES128CBC(String str)
throws UnsupportedEncodingException, NoSuchAlgorithmException,NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] textBytes = str.getBytes("UTF-8");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = null;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return Base64.encodeBase64String(cipher.doFinal(textBytes));
}
/*
* AES128 CBC 복호화 함수
* 128은 암호화 키 길이를 의미한다.
* 128bit = 16byte (1Byte=8bit * 16 = 128)
*/
public static String decAES128CBC(String str)
throws UnsupportedEncodingException, NoSuchAlgorithmException,NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] textBytes = Base64.decodeBase64(str);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
SecretKeySpec newKey = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(textBytes), "UTF-8");
}
}
- 로그인과 회원가입에 사용할 암호화 알고리즘도 찾아서 추가해줬다.
package poly.util;
public class EncryptTest {
public static void main(String[] args) throws Exception {
System.out.println("------------------------------");
System.out.println("해시 암호화 알고리즘 테스트");
//암호화 문자열
String str = "암호화할 문자열";
//복호화가 불가능한 해시 암호화 알고리즘 실행
String hashEnc = EncryptUtil.encHashSHA256(str);
//해시 암호화 알고리즘 결과 출력
System.out.println("hashEnc : " + hashEnc);
System.out.println("------------------------------");
System.out.println("AES128-CBC 암, 복호화 알고리즘");
//AES128-CBC 암호화 알고리즘 실행
String enc = EncryptUtil.encAES128CBC(str);
//AES128-CBC 암호화 알고리즘 결과 출력
System.out.println("enc : " + enc);
//AES128-CBC 복호화 알고리즘 실행
String dec = EncryptUtil.decAES128CBC(enc);
//AES128-CBC 복호화 알고리즘 결과 출력
System.out.println("dec : " + dec);
System.out.println("------------------------------");
}
}
- 테스트 코드도 작성해서 돌려봤다.
- 암호화 로직은 정상 작동한다.
미해결
데이터 베이스 연동 테스트 코드 작성 및 정상 작동 확인
- 아직 테스트 코드 작성을 못 했다.
- 데이터 베이스를 aws에다 올려서 연결하는 게 처음이라 제대로 연동을 한건지 아직 알 수 없다.
- 테스트 코드 작성 혹은 회원가입 로직 후 테스트 필요하다.
- 테스트 코드 작성 혹은 회원가입 로직 후 테스트 필요하다.
'발전소 > 동아리 및 스터디' 카테고리의 다른 글
[코딩&리뷰] : 스터디 5회차 (0) | 2022.02.07 |
---|---|
[코딩&리뷰] : 스터디 4회차 (0) | 2022.01.26 |
[코딩&리뷰] : 스터디 3회차 (0) | 2022.01.19 |
[코딩&리뷰] : 스터디 2회차 (0) | 2022.01.12 |
[동아리 및 스터디] : [코딩&리뷰] : 소개 (0) | 2022.01.03 |
댓글