안녕하세요! 예시에서 person.get지갑().get신분증().findAge() >= 19 를 person.isAgeGreaterThanOrEqualTo(19) 메서드로 만들면 person 내부에서 다시 지갑.get신분증().findAge() 형태가 되는데, 이 경우 지갑.isAge..(19) 같은 메서드를 계속 만드는지 끊어내는 기준이 있는지 궁금합니다.
안녕하세요, 우빈님! 정말 좋은 강의를 제공해주셔서 감사합니다. 제가 고민하던 주제에 관해 다뤄주셔서 정말 흥미를 가지고 강의를 들을 수 있었습니다 ☺ 강의를 다 들은 시점에 개발 공부를 시작한 이래로 늘 가지고 있던 오랜 궁금한 점이 있어서 이렇게 질문을 남기게 되었습니다! 조금 장문이 될 수도 있을 거 같네요 🥲 스프링을 사용해서 개발하게 되면 제일 처음 배우는 구조가 Controller -> Service -> Repository 이런 구조인데요, 보통 비즈니스 로직은 Service 에 작성된다고 배웠습니다. Service 레이어에서 모든 게 처리되고 비즈니스 로직이 도메인 쪽에 위치하지 못 하게 되는 것 같습니다. 애초에 도메인이라는 개념 자체가 없고 바로 JPA 엔티티를 만들고 @Service 어노테이션이 붙은 클래스에서 모든 걸 다 처리하는 거 같아요. 제가 국비 교육에서 배운 팀 프로젝트나 3개월 간 짧게 경험해 본 실무에서도 다 이렇게 코드를 짰던 거 같습니다. 공부하다보니 이런 걸 트랜잭션 스크립트 라고 하는 거 같더라구요. 그런데 아무리 생각해도 이런 방식은 객체지향적이지 않다는 생각을 지울 수가 없었습니다. 제가 객체지향의 사실과 오해, 오브젝트 등에서 읽은 객체의 역할, 책임, 협력 이런 것들을 적용해보지 못 하는 것 같아서 객체지향적으로 코드를 짜는 것은 어떤 것일까 하는 갈망이 늘 있었습니다. 이런 갈망이 있어서 이번 강의를 더 간절히 기다렸던 거 같아요 😀 이번 강의는 스프링이나 데이터베이스를 전혀 사용하지 않고 순수 자바로만 객체지향에 대해서 설명을 해주셨는데요, 만약 스프링과 데이터베이스 기술(JPA) 등을 사용한다고 할 때는 어떻게 처리하시는 지 궁금합니다. 이와 관련해서 제가 공부한 건 JPA 엔티티와 별개로 도메인 역할을 하는 클래스를 만들고 해당 클래스에서 비즈니스 로직을 처리하게 하고 도메인 객체를 JPA 엔티티로 변환해서 데이터베이스에 저장하는 방식이었습니다. (도메인 객체와 영속성 객체를 분리한 것이라고 할 수 있을 거 같습니다.) 이렇게 하면 도메인 객체 안에 있는 비즈니스 로직을 단위 테스트 하기도 쉽고(도메인 객체는 순수 자바(코틀린) 코드로만 돼 있으니까요!) 데이터베이스 중심적이지 않고 도메인 중심적으로 생각을 할 수 있는 것 같다는 인상을 받았습니다. 다만 이렇게 할 때는 어떤 개념을 통해 도메인을 추출해야할 지, 그리고 책임을 어떤 도메인에 넣어야 할 지 고민할 게 많더라구요. 근데 약간 소설의 등장인물을 설정하는 거 같아서 재미는 있었습니다 ㅎㅎ 이렇게 접근을 하니 @Service 어노테이션이 붙은 클래스는 단순히 객체들이 모여서 서로 협력할 수 있게 해주는 광장(?) 이라고 해야될까요, 중개해주는 역할만 하게 되는 것 같다는 생각을 해보기도 했습니다. 이러다 보니 DDD 에 대해서도 많이 관심을 갖게 됐습니다. 도메인 위주로 설계 하고 도메인에게 적절한 역할을 할당하는 방식인 거 같아서요. 그리고 아키텍처에도 많이 관심이 생기더라구요. 단순히 Controller -> Service -> Repositoy 구조의 레이어드 아키텍처에선 처음 언급한 문제를 해결하는게 쉽지 않아 보였거든요. 주절주절 질문이 길었는데 요약을 해보자면 다음과 같습니다. 스프링, JPA 같은 기술과 접목했을 때 이번 강의에서 배운 추상, 객체지향적 개발 방식을 어떻게 적용할 수 있을까요? 이후로 공부할 만한 주제로 DDD, 아키텍처 등이 적절할까요? 비전공으로 혼자서 백엔드 개발자 취업 준비 중이라 질문할 곳이 마땅치 않아 여기에 이렇게 남기는 점 미리 양해를 구합니다 😭
실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요 강사님! 테스트 코드와 관련하여 질문이 있습니다. 프로젝트를 해보면서 고민이 되었던 것 중 하나가 테스트 코드를 작성할 때 명확한 메서드명을 작성해야하는가에 대해서 고민이었습니다. @Dipslay로 어떤 테스트인지 명시를 하고 있음에도 메서드명이 명확한 것이 좋을까요?
안녕하세요! 강의 너무 잘 듣고 있습니다! 개발을 진행하면서 항상 네이밍 관련된 부분이 정말 어려운거 같네요ㅜㅜ 추상화 레벨 파트를 수강하다가 궁금한 점이 있어서 질문드립니다! 같은 boolean형을 반환하는 메서드인데 doesUserWinTheGame() 처럼 doesXxx() 와, isAllCellIsOpened() 처럼 isXxx() 두 가지 형태를 사용하셨는데, 주로 어떤 상황에서 does를 사용하시고, is를 사용하시는지 궁금해서 질문드립니다!
안녕하세요 우빈님! 이 강의 오픈 소식을 볼려고 6월 내내 인프런 알림을 얼마나 눌렀는지 아시나요? ㅠㅠㅋㅋㅋ 어제 오픈해주시자마자 열심히 듣는 중입니다! 좋은 강의 감사드립니다 ㅎㅎ 질문 제목처럼 setter 와 관련한 질문이 있습니다. 실무에서 코틀린을 사용하신다고 하셨는데, var 로 프로퍼티를 만들게 되면 외부에서 접근해서 setter 처럼 바로 값을 변경할 수 있는게 문제가 되지 않을까 늘 고민이 많이 됐습니다. 접근 제어자를 private 으로 만들어주고 해당 프로퍼티의 값을 업데이트해주는 내부 메서드를 만드는 식으로 해야되나 이런 고민도 해봤구요. 아니면 팀원들과 잘 약속해서 써야하나? 이런 고민도 해봤습니다. (물론 저는 지금 취준생이지만요 ㅎㅎ) getter 도 처음에는 자제하는게 좋다고 하셨는데, 이러면 val 도 사용을 자제해야되는 것일까요..? var, val 둘 다 되게 편하고 좋은 거 같은데 이런 걸 아예 사용 안 하는 것은 코틀린을 쓰는 의미가 크게 반감되는 거 같기도 해서, 뭔가 다른 더 좋은 방법이 있을 것 같기도 합니다. 우빈님 같은 경우에는 이런 문제를 어떻게 해결하시는 지 궁금합니다! 감사합니다.
실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)
안녕하세요 강사님 강의를 들으면서 이번에 처음 JPA를 알아가고 있는 중입니다. 헌데 궁금한점이 JPA 특성인것인지 아니면 H2 DB 문제인지 그것도 아니면 제 Intellij 설정 문제인지 모르겠지만 select가 두번이 되는 이유가 무엇때문이지 궁금해서 질문 드립니다. 실질적인 동작에 문제가 생기지는 않겠지만 N+1 관련 강의를 듣다보니 불필요하게 쿼리가 호출되면 안좋은것 같아 여쭈어 봅니다. @Test @DisplayName("대출 기록이 없는 유저도 응답에 포함") fun getUserLoanHistoriesTest() { //given userRepository.save(User("A",null)) //when val results = userService.getUserLoanHistories() //then assertThat(results).hasSize(1) assertThat(results[0].name).isEqualTo("A") assertThat(results[0].books).isEmpty() } 위의 코드를 동작시켰을때 Hibernate: insert into user (id, age, name) values (default, ?, ?) Hibernate: select user0_.id as id1_1_, user0_.age as age2_1_, user0_.name as name3_1_ from user user0_ Hibernate: select userloanhi0_.user_id as user_id4_2_0_, userloanhi0_.id as id1_2_0_, userloanhi0_.id as id1_2_1_, userloanhi0_.book_name as book_nam2_2_1_, userloanhi0_.status as status3_2_1_, userloanhi0_.user_id as user_id4_2_1_ from user_loan_history userloanhi0_ where userloanhi0_.user_id=? Hibernate: select user0_.id as id1_1_, user0_.age as age2_1_, user0_.name as name3_1_ from user user0_ Hibernate: select userloanhi0_.user_id as user_id4_2_0_, userloanhi0_.id as id1_2_0_, userloanhi0_.id as id1_2_1_, userloanhi0_.book_name as book_nam2_2_1_, userloanhi0_.status as status3_2_1_, userloanhi0_.user_id as user_id4_2_1_ from user_loan_history userloanhi0_ where userloanhi0_.user_id=? Hibernate: delete from user where id=? 출력 부분에 위에 처럼 뜨는데요 제가 이해한 바로는 insert는 save 때문에 한번인 반면에 getUserLoanHistoreies 부분에 findAll로 한번의 쿼리를 불러오는 거라서 user와 와 userloanhistory 테이블을 각각 한번 씩 조회 해야 하지 않는가 싶어서 질문 드립니다. 추가로 확인해 보보니 LEFT join에 fetch를 추가했을때 위의 select user와 user_loan_history는 left join 쿼리문으로 변한는 반면 아래의 select user와 user_loan_history는 남아 있습니다. 따로 user를 find 하는 곳이 없는 것 같은데 이런 현상이 나오는 이유가 뭘까요?.... 추측 하기로는 AfterEach로 clear할때 deleteAll을 사용해서 사용했던 테이블을 모두 select 하는건가 싶긴 한데... 제가 이해를 잘 못해서 그런건지 이부분이 어렵네요....
orm.py 를 다음과 같이 작성했고 from sqlalchemy import Boolean, Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() # base란 클래스로 db모델링 class ToDo(Base): __tablename__ = 'todo' # table이름 id = Column(Integer, primary_key=True, index=True) content = Column(String(256), nullable=False) is_done = Column(Boolean, nullable=False) # todo 객체의 출력을 쉽게 보기위해 repr def __repr__(self): return f'<ToDo(id={self.id}, content={self.content})>, is_done={self.is_done}' connection.py 도 다음과 같이 작성했으나 from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker DATABASE_URL = "mysql+pymysql://root:todos@127.0.0.1:3306/todos" engine = create_engine(DATABASE_URL, echo=True) # echo는 쿼리의 처리를 확인 SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine) 강의안에서 말씀하신대로 import까지 완료하고 명령어 session.scalars(select(ToDo)) 를 실행하는 과정에서 자꾸 이렇게 뜹니다 에러가 한두개가 아니라서 감도 안잡힙니다.. 도와주세요 2024-06-06 20:53:23,126 INFO sqlalchemy.engine.Engine SELECT todo.id , todo.content, todo.is _done FROM todo 2024-06-06 20:53:23,126 INFO sqlalchemy.engine.Engine [cached since 368.2s ago] {} Traceback (most recent call last): File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1967, in _exec_single_context self.dialect.do _execute( File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ default.py ", line 924, in do_execute cursor.execute(statement, parameters) File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ cursors.py ", line 153, in execute result = self._query(query) ^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ cursors.py ", line 322, in _query conn.query(q) File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 563, in query self._affected_rows = self._read_query_result(unbuffered=unbuffered) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 825, in _read_query_result result.read () File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 1199, in read first_packet = self.connection._read_packet() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 775, in _read_packet packet.raise_for_error() File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ protocol.py ", line 219, in raise_for_error err.raise_mysql_exception(self._data) File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ err.py ", line 150, in raise_mysql_exception raise errorclass(errno, errval) pymysql.err.OperationalError: (1054, "Unknown column 'todo.content' in 'field list'") The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Program Files\JetBrains\PyCharm 2024.1.2\plugins\python\helpers\pydev\ pydevconsole.py ", line 364, in runcode coro = func() ^^^^^^ File "<input>", line 1, in <module> File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\orm\ session.py ", line 2459, in scalars return self._execute_internal( ^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\orm\ session.py ", line 2236, in _execute_internal result: Result[Any] = compile_state_cls.orm_execute_statement( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\orm\ context.py ", line 293, in orm_execute_statement result = conn.execute( ^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1418, in execute return meth( ^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\sql\ elements.py ", line 515, in _execute_on_connection return connection._execute_clauseelement( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1640, in _execute_clauseelement ret = self._execute_context( ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1846, in _execute_context return self._exec_single_context( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1986, in _exec_single_context self._handle_dbapi_exception( File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 2353, in _handle_dbapi_exception raise sqlalchemy_exception.with_traceback(exc_info[2]) from e File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ base.py ", line 1967, in _exec_single_context self.dialect.do _execute( File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\sqlalchemy\engine\ default.py ", line 924, in do_execute cursor.execute(statement, parameters) File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ cursors.py ", line 153, in execute result = self._query(query) ^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ cursors.py ", line 322, in _query conn.query(q) File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 563, in query self._affected_rows = self._read_query_result(unbuffered=unbuffered) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 825, in _read_query_result result.read () File "C:\Users\yhkim\inflern_project\todos\Lib\site-packages\pymysql\ connections.py ", line 1199, in read first_packet = self.connection._read_packet() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy module이 설치되어있음에도 불구하고 위와 같이 서버를 실행시키면 no module named 'sqlalchemy' 라는 오류가 뜨는데 어떻게 해야하나요.. +추가로 위의 문제를 해결하기 위해서 시도했던 방법으로, 아래와 같이 flask_sqlalchemy를 통해서 sqlalchemy를 불러왔지만 from flask_sqlalchemy import SQLAlchemy 마찬가지로 flask_sqlalchemy에 대해서도 찾을 수 없다는 오류가 뜹니다..
WSL2를 사용해 강의를 따라가고 있습니다. 파이참에서 파이썬 콘솔을 실행하면 위와 같이 재시작, 정지, 한 줄씩 실행 버튼이 잘 보이는 경우가 있고, 아래처럼 보이지 않는 경우가 있습니다. 이런 경우는 뭐가 문제일까요? 또한, 파이썬 콘솔에서 코드를 작성한 이후 엔터를 입력해도 다음 줄로 넘어가기만 하고 실행이 되지 않습니다. 한 줄씩 실행하려면 alt + shift + e 이렇게 입력해야 하는데, 이렇게 코드를 실행하게 되면 여러 줄은 또 실행되지 않습니다... 뭐가 문제인지 알려주시면 감사하겠습니다.