Tutorial

여기 따라하면 기본적인건 해볼 수 있다.

 

설치

서버사이드(Java/Maven)

maven의존성

springframework에서 지원하는 java버전이 있고

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-graphql</artifactId>
        </dependency>

 

expediagroup에서 지원하는 kotlin버전이 있다.

        <dependency>
            <groupId>com.expediagroup</groupId>
            <artifactId>graphql-kotlin-spring-server</artifactId>
            <version>7.0.1</version>
        </dependency>

후자가 Resolver(리졸버)만 구현하면 스키마를 자동생성해주고 kotlin 타입과 연동되는등 kotlin을 사용한다면 더 편리한 측면이 있지만, 여기서는 전자를 사용한 기준으로 서술한다.

expediagroup 아티펙트의 경우, 내 경우엔 /graphql 404문제가 해결이 안돼서 springframework를 쓰기로 했다.
추가로 조사해보니 webmvc대신 webflux로 교체해야 호환되는 이슈도 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.

 

클라이언트 사이드(React/Next.js)

관련 library설치

npm install @apollo/client graphql

 

아폴로?

Apollo Client는 JavaScript 어플리케이션에서 GraphQL API와 통신할 수 있게 해주는 라이브러리입니다.

Facebook에서는 GraphQL을 발명했으며, Relay라는 GraphQL 클라이언트를 만들어 공개했습니다. 그러나 Apollo가 Relay보다 더 널리 사용되는 이유는
사용자 친화적: Apollo는 사용자 친화적이고, 초보자에게 친숙하며, 설정이 상대적으로 간단합니다. 문서화도 잘 되어 있어, 개발자들이 쉽게 접근하고 사용할 수 있습니다.
커뮤니티 지원: Apollo는 강력한 커뮤니티 지원을 받고 있으며, 다양한 추가 기능과 툴이 개발되고 있습니다. 또한 꾸준한 업데이트와 개선이 이루어지고 있어, 더 많은 개발자들이 Apollo를 선호하게 되었습니다.

 

코드

이번 문서 에서는 특정문제에 대해서 제출된 횟수를 리턴하는 예제를 해보기로 하자.

프로젝트 구성에 대해서는 여기를 보고오자.

 

서버사이드

Query

1. GraphQL 스키마 정의

src/main/resources/graphql/schema.graphqls 파일에 쿼리를 추가

type Query {
  submissionCountByProblem(problemId: ID!): Int
}


2. 서비스 수정

SubmissionService에 getSubmissionCountByProblem매서드를 추가하여 문제별 제출 수를 가져옴 

@Service
class SubmissionService(private val submissionRepository: SubmissionRepository) {

    // 아래는 기존에 존재하던 매서드
    fun submitProblem(userId: Long, problemId: Int, code: String): Submission {
        val submission = Submission(
            userId = userId,
            problemId = problemId,
            code = code,
            status = "PENDING" // 초기 상태
        )
        return submissionRepository.save(submission)
    }

    // 아래 매서드 추가
    fun getSubmissionCountByProblem(problemId: Int): Int {
        return submissionRepository.countByProblemId(problemId)
    }
}

 

3. 컨트롤러에 로직 추가

컨트롤러에 @QueryMapping 어노테이션을 사용하여 submissionCountByProblem GraphQL쿼리를 처리하는 메서드를 추가(엔드포인트 추가는 아니지만 약간 유사)

package com.sevity.problemservice.controller

import ...

@RestController
class SubmissionController(private val submissionService: SubmissionService) {

    // 기존 코드
    // ...

    @QueryMapping
    fun submissionCountByProblem(@Argument problemId: Int): Int {
        return submissionService.getSubmissionCountByProblem(problemId)
    }
}

 

Mutation

//TBD

 

클라이언트사이드

본격적으로 클라이언트 사이드 코딩을 하기전에

https://localhost:9993/graphiql 

위 url에 접속해서 아래 처럼 날려볼 수 있다.

query  {submissionCountByProblem(problemId: 13)}

그럼 결과가 다음처럼 보일 것이다.

 

apollo library 사용을 위해 아래 코드를 기본적으로 넣어준다.

// src/apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://sevity.com:9993/graphql',  // GraphQL 서버 URL
  cache: new InMemoryCache(),
});

export default client;

 

그다음 _app.js를 수정해서 각 페이지에서 해당 기능을 사용할 수 있게 해준다.

import ...
import { ApolloProvider } from '@apollo/client';
import client from '../apolloClient';  // Adjust the path if necessary

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp;

 

실제 조회하는 코드를 [id].js에 넣어준다.(제출 횟수 및 관련 부분)

...
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';

// 아래 스트링 방식이 후진적으로 보일 수 있는데, 간접적으로 문법검사나, 자동생성 툴이 존재한다.
// 하지만, 결국 서버측에 문자열로 graphQL을 보내야한다. 현재 graphQL의 한계점.
const GET_PROBLEM_SUBMISSION_COUNT = gql`
  query GetProblemSubmissionCount($problemId: Int!) {
    submissionCountByProblem(problemId: $problemId)
  }
`;


const Problem = () => {
  ...
  const { data, loading, error } = useQuery(GET_PROBLEM_SUBMISSION_COUNT, {
    variables: { problemId: Number(id) },
    skip: !id  // id가 없는 경우 쿼리를 건너뜁니다.
  });

  // JavaScript의 옵셔널 체이닝(Optional Chaining)사용('?.'부분들)
  const submissionCount = data?.submissionCountByProblem;
  if(data)
    console.log('GraphQL response:', JSON.stringify(data, null, 2));

...
  
  
  return (
    <div className="container">
      <div className="alert alert-success mt-3">username: {username} </div>
      {problem ? (
        <>
          <h1>{problem.title}</h1>
          <p>{problem.description}</p>
          <p><strong>예제 입력:</strong> {problem.exampleInput}</p>
          <p><strong>예제 출력:</strong> {problem.exampleOutput}</p>
          <p><strong>실제 입력:</strong> {problem.realInput}</p>
          <p><strong>실제 출력:</strong> {problem.realOutput}</p>
          {submissionCount && (
            <p><strong>제출 횟수:</strong> {submissionCount}</p>
          )}
          <textarea
            className="form-control"
            rows="10"
            value={sourceCode}
            onChange={(e) => setSourceCode(e.target.value)}
          ></textarea>
          <button className="btn btn-primary" onClick={handleSubmit}>
            제출
          </button>
        </>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default Problem;

 

 

트러블슈팅

/graphql 404

src/main/resources/graphql 폴더와 그안에 anyname.graphqls 파일을 생성안하면 /graphql 경로접근시 404뜨는 문제가 있어서 반나절 이상 소모했다 ㅠ

여기에도 기록함

 

expediagroup 아티펙트의 경우, 위의 해결책을 적용해도 여전히 404가 떴고, 추가로 조사해보니 webmvc대신 webflux로 교체해야 하는 이슈가 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.

반응형

'Programming > SpringBoot' 카테고리의 다른 글

webflux  (0) 2025.05.31
WebSocket  (1) 2025.05.22
Spring에서 gRPC연동하기  (2) 2023.10.11
maven dependency  (0) 2023.10.11
Spring Boot에서 endpoint접근을 http에서 https로 변경하기  (0) 2023.10.09

MSA구조에서 쿠키를 다루다가 CORS (Cross-Origin Resource Sharing) 문제를 피하기 위해서

https로 변경이 필요해서 시도해 보게 되었다.

서버사이드는 여기 참조

 

먼저 인증서를 발급한다.


개발용 인증서를 생성하기 위해 openssl을 사용할 수 있습니다. 터미널에서 아래 명령을 실행

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

암호를 입력하라는 창이뜬다.

이 명령은 1년 동안 유효한 cert.pem과 key.pem 파일을 생성

 

 

 

기존에 npm start로 시작했다면, 아래처럼 start대신 server.js로 변경

 

다음처럼 server.js를 작성하고, project root에 둔다.

console.log('sevity Server is starting...');

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
    const options = {
        key: fs.readFileSync('./key.pem'),
        cert: fs.readFileSync('./cert.pem'),
        passphrase: 'abcd123$'
    };

    createServer(options, (req, res) => {
        const parsedUrl = parse(req.url, true);
        handle(req, res, parsedUrl);
    }).listen(9992, (err) => {
        if (err) throw err;
        console.log('> Ready on https://localhost:9992');
    });
});

 

npm start로 시작해보면 http대신 https로 되는걸 확인가능.

반응형

'Programming > node.js' 카테고리의 다른 글

node.js/vscode 환경에서 디버깅 환경 구축하기  (0) 2023.08.06

MSA구조에서 Spring Service간 통신에 REST를 써도되지만 gRPC를 쓸 수도 있다.

이경우 설정방법에 대해서 경험한 바를 여기 적는다.

 

pom.xml 에 의존성 추가

서버/클라이언트 공통

아래 내용을 서버/클라이언트 pom.xml에 공히 추가하면 되고,

src/main/kotlin/이냐 src/main/java/냐 이부분만 서로 다르게 수정해주면 된다.

protobuf-javacom.google.protobuf:protoc:3.12.4 이부분등 버전을 맞춰주지 않으면 빌드과정에서 오류가 나는 경우가 있었으니 주의.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <dependencies>

        <!-- gRPC -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.12.4</version>  <!--이 버전을 아래쪽 ptoroc버전과 맞춰야 함-->
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.41.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.41.0</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <!--Maven 빌드 과정 중에 운영 체제(OS)에 관한 정보를 제공하고 설정하는 데 도움을 줍니다. -->
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.0</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.4:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}</pluginArtifact>
                    <outputBaseDirectory>src/main/kotlin/</outputBaseDirectory>
                    <outputDirectory>src/main/kotlin/</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                        <configuration>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 

클라이언트 전용

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-autoconfigure</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>

위의 내용은 필수는 아니나 코드가 좀 더 깔끔해지게 도와준다.

 

protobuf 파일추가

아래와 같이 .proto 파일을 제작해서 src/main/proto 폴더안에 둔다(다른곳에 두어도 되며, 프로젝트간 공용위치에 두어도 된다)

//session_service.proto
// Version: 1.0.0

syntax = "proto3";

package com.sevity.authservice.grpc;

service SesseionService {
    rpc GetUserId (SessionRequest) returns (UserResponse) {};
}

message SessionRequest {
    string sessionId = 1;
}

message UserResponse {
    int32 userId = 1;
}

중요한건 아래 트러블슈팅에서도 나오지만 package명을 서버/클라이언트가 다르게 하면 못찾는다는 오류가 떴다.

 

빌드

mvn clean install 등을 수행하면 .proto 파일을 컴파일해서 다음과 같은 2개의 java파일을 자동으로 생성해준다.

위치또한 pom.xml파일에 지정된 대로 생성된다.

파일2개중 하나만 생성된적도 있었는데 여기보고 해결했던것 같다.

protoc를 터미널에서 직접 사용해서 .proto파일을 컴파일하는 것도 가능하긴하지만, mvn에 통합해서 운용하는게 훨씬 편하고, protoc 사용과정에서 직접빌드해야하는등 우여곡절도 발생했다.

서버코드 제작

아래처럼 생성된 java파일들 (grpc패키지)을 import해주고,

gRPC서버측 구현을 해준다(SessionService의 getUserId 함수)

package com.sevity.authservice.service;

import com.sevity.authservice.grpc.SesseionServiceGrpc;
import com.sevity.authservice.grpc.SessionService.SessionRequest;
import com.sevity.authservice.grpc.SessionService.UserResponse;

import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import io.grpc.Status;


@Service
public class SessionServiceImpl extends SesseionServiceGrpc.SesseionServiceImplBase {
    @Override
    public void getUserId(SessionRequest request, StreamObserver<UserResponse> responseObserver) {
        String sessionId = request.getSessionId();

        UserResponse response = UserResponse.newBuilder().setUserId(sessionId).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

 

application.properties에서 포트설정해주고(이때 src/main/resources뿐 아니라 src/test/resources에 있는 파일도 해줘야함에 주의)

# in application.properties
grpc.server.port = 50051

 

다음처럼 gRPC서버 띄욱 listen작업도 해줘야 했다.

package com.sevity.authservice.config;

import ...
@Configuration
public class GrpcServerConfig {
    private static final Logger logger = LoggerFactory.getLogger(GrpcServerConfig.class);

    @Autowired
    private SessionServiceImpl sessionService;
    
    private Server server;
    
    @Value("${grpc.server.port}")
    private int port;

    @PostConstruct
    public void startServer() throws IOException {
        server = ServerBuilder
            .forPort(port)
            .addService(sessionService)  // Your gRPC service implementation
            .build();

        server.start();
        logger.info("sevity gRPC server started on port {}", port);
        logger.info("sevity gRPC service name: {}", sessionService.getClass().getSimpleName());        
    }

    @PreDestroy
    public void stopServer() {
        if (server != null) {
            server.shutdown();
        }
        logger.info("sevity gRPC server stopped");
    }
}

 

클라이언트코드 제작

application.properties에 아래줄 추가

# GRPC
grpc.client.authService.address=static://sevity.com:50051
grpc.client.authService.negotiationType=PLAINTEXT

 

빌드

maven,kotlin환경이었는데, 빌드과정은 서버측과 큰차이가 없다.(같은 방법으로 빌드하면 된다)

(생성되는 파일도 여전히 .java이며 kotlin과 통합에 문제가 없었다)

 

호출하는 코드는 아래와 같다(코틀린임에 주의)

/submit 매핑과, cookie, session관련 처리때문에 복잡하나 그 부분은 제외하고 gRPC stub에 대한 내용만 눈여겨보자.

(서버측에 비해서 필요할때 문맥중간에서 요청하게된다)

@RestController
class SubmissionController {

    @GrpcClient("authService")
    private lateinit var sessionServiceStub: SesseionServiceGrpc.SesseionServiceBlockingStub

    @PostMapping("/submit")
    fun submitCode(request: HttpServletRequest): ResponseEntity<String> {
        val cookies = request.cookies
        val sessionId = cookies?.find { it.name == "SESSION" }?.value
            ?: return ResponseEntity("Session ID not found", HttpStatus.UNAUTHORIZED)

        val request2 = SessionRequest.newBuilder().setSessionId(sessionId).build()
        try {
            val response = sessionServiceStub.getUserId(request2)
            println("Received user ID: ${response.userId}")
            return ResponseEntity("Code submitted", HttpStatus.OK)
        } catch (e: InvocationTargetException) {
            e.targetException.printStackTrace()
        }

        // 나머지 로직
        return ResponseEntity("Code submitted", HttpStatus.OK)
    }
}

 

 

트러블슈팅

io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found

.proto 파일은 파일내 package경로까지 완전히 동일한 파일을 사용하지 않으면 못찾는다고 에러가 났다.

이거때문에 한참헤멤 ㅠ 여기참조. 유일한 솔루션인지는 잘 모르겠으나,

서버기준으로 package명까지 동일하게 맞춰주니 해결됨.

 

 

반응형

'Programming > SpringBoot' 카테고리의 다른 글

WebSocket  (1) 2025.05.22
Spring에서 graphQL적용하기  (0) 2023.10.13
maven dependency  (0) 2023.10.11
Spring Boot에서 endpoint접근을 http에서 https로 변경하기  (0) 2023.10.09
Spring Boot에서의 세션 관리  (2) 2023.10.08

pom.xml 변경하고 (버전변경등) 디펜던시 업데이트가 잘 안되면,

~/.m2 폴더안의 내용을 지워주면 다시 다운받는다.

(프로젝트 root가 아닌 user home디렉토리임에 주의)

반응형

'Programming > SpringBoot' 카테고리의 다른 글

WebSocket  (1) 2025.05.22
Spring에서 graphQL적용하기  (0) 2023.10.13
Spring에서 gRPC연동하기  (2) 2023.10.11
Spring Boot에서 endpoint접근을 http에서 https로 변경하기  (0) 2023.10.09
Spring Boot에서의 세션 관리  (2) 2023.10.08

아래는 개발과정에서 쓰는 임시 인증서를 사용하여 https로 서비스를 운용하는 방법에 대한 설명이다.

세션-쿠키관련해서 https로 해야할일이 생겨서 적용해봤다.

 

1. 자체 서명된 인증서 생성:

keytool -genkey -alias selfsigned_localhost_sslserver -keyalg RSA -keysize 2048 -validity 3650 -keystore ssl-server.jks -keypass your_password -storepass your_password

생성된 ssl-server.jks 파일을 src/main/resources 폴더에 복사한다.

 

2. Spring Boot 설정:

application.properties 파일에 아래 설정을 추가

server.port=8443
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-password=your_password
server.ssl.key-store-type=JKS
server.ssl.key-alias=selfsigned_localhost_sslserver

 

반응형

'Programming > SpringBoot' 카테고리의 다른 글

WebSocket  (1) 2025.05.22
Spring에서 graphQL적용하기  (0) 2023.10.13
Spring에서 gRPC연동하기  (2) 2023.10.11
maven dependency  (0) 2023.10.11
Spring Boot에서의 세션 관리  (2) 2023.10.08

현재 만들어 보고 있는 online judge 프로젝트의 서비스 구성은 다음과 같다.(관련 있는 2개만 표시. 실제로는 7개)

인증 서비스 (Backend): 사용자의 회원 가입, 로그인, 로그아웃, 세션 관리 등을 담당
인증 서비스 (Frontend): 사용자 인터페이스를 제공 (로그인 폼, 회원가입 폼 등)

 

한가지 알아두면 좋은점은 Spring Boot의 경우 /login, /logout endpoint의 경우 직접 정의하지 않아도 자동으로 처리한다는 점이다.(이점 때문에 디버깅시 많이 헷갈렸다ㅠ)

 

세션은 서버에서 브라우저로 set-cookie 헤더를 통해서 세션아이디를 부여한다.

아래처럼 브라우저 개발자 도구에서 확인가능하다(애플리케이션탭 > 쿠키섹션)

 

서버의 세션과 브라우저(클라이언트)의 쿠키 개념

세션을 서버에서 생성하고 세션id를 set-cookie를 통해서 브라우저(클라이언트)로 전달한다.

이때 쿠키는 브라우저를 종료해도 유지되는 지속쿠키를 쓰고 만료시점을 정의할수도 있고, 세션쿠키를 쓰면 브라우저 종료시 자동으로 쿠키도 삭제된다.

Expres/Max-Age 컬럼을 보면 세션쿠키와 지속쿠키의 차이를 볼 수 있다.

서버의 세션의 경우 지속시간을 application.properties에 다음과 같이 지정할 수 있다.

server.servlet.session.timeout=30m

현재 내가 테스트중인 프로젝트에서는 /login 성공시 세션쿠키가 발급되며, 세션 타임아웃의 효과는 확인이 안되었다.

일단은 이정도에서 더 깊이 안파고 넘어가기로 한다.

 

트러블슈팅

http에서 포트가 서로다른 서비스(MSA)간 연동하기

아래 크롬 설명에 따르면 https를 쓰고 secure 옵션을 주어야 SameSite=None으로 지정하면서 포트가 달라도 쿠키저장이 된다는것 같다.(관련글, 관련글2)

 

set-cookie로 응답을 제대로 했음에도 브라우저에 쿠키저장이 안될때

클라이언트에서 서버로 요청할때 credential을 보내줘야 했다(이것땜에 한참헤맴 ㅠ)

      const response = await axios.post('https://sevity.com:9991/login', `username=${username}&password=${password}`, {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        withCredentials: true,  // 여기, 이 줄 없으면 브라우저에 쿠키저장 안된다!!
      });

 

getSession(true)의 안전성

HttpSession session = request.getSession(false); 를 하면 존재하는 세션정보를 가져오고, false자리에 true를 넣으면 없으면 생성하라는 의미인데, 이렇게 하면 (내가만든세션), (SpringSecurity에 의해 아직 없지만 생성될 세션) 이렇게 두벌이 될까봐 우려했는데, 나중에 확인해보니 그렇지는 않았다. 따라서 항상 true로 호출해도 무방한 것 같다.

 

단 여기를 보면 멀티스레드 환경에서 레이스 컨디션에 의해 중복세션과 중복쿠키가 생성되는 경우는 있는 것 같다. 해결책은 아래처럼 동기화블록내에 생성과 처리를 묶어주면 되긴할듯(현재 내 구현에서는 그렇게 까지 하진 않았다)

synchronized (request) {
    HttpSession session = request.getSession(true);
    // ... 세션 사용 코드 ...
}

 

 

TMI

/login에서 반환되는 response.data 값과 SESSION쿠키의 값은 동일하나, SESSION쿠키의 경우 base64로 인코딩 되어 있다.

//response.data: c8545bb7-c90b-4d69-9128-08efd0a73866
//SESSION(쿠키): Yzg1NDViYjctYzkwYi00ZDY5LTkxMjgtMDhlZmQwYTczODY2

let encodedSessionId = 'Yzg1NDViYjctYzkwYi00ZDY5LTkxMjgtMDhlZmQwYTczODY2';
let decodedSessionId = atob(encodedSessionId);
console.log(decodedSessionId);  // 출력: c8545bb7-c90b-4d69-9128-08efd0a73866

 

반응형

'Programming > SpringBoot' 카테고리의 다른 글

WebSocket  (1) 2025.05.22
Spring에서 graphQL적용하기  (0) 2023.10.13
Spring에서 gRPC연동하기  (2) 2023.10.11
maven dependency  (0) 2023.10.11
Spring Boot에서 endpoint접근을 http에서 https로 변경하기  (0) 2023.10.09

Mypy

Mypy는 Python 코드에 대해 정적 타입 검사를 수행하는 도구입니다.

이는 TypeScript의 컴파일러와 유사한 역할을 수행하며, 코드에서 타입 오류를 찾아내는 데 도움이 됩니다​.

 

예제:
먼저 mypy를 설치합니다:

pip install mypy


다음은 mypy를 사용한 Python 코드 예제입니다:(name에 대해 str이라는 어노테이션을 했음에 주목. python 3.5기능)

# 파일명: example.py

def greeting(name: str) -> str:
    return "Hello, " + name

age: int = "25"  # 이 줄은 타입 오류를 발생시킵니다.

print(greeting("Alice"))


터미널에서 mypy를 사용하여 코드를 검사합니다:

mypy example.py

이 명령을 실행하면, mypy는 age: int = "25"라인에서 타입 오류를 발견하고 이를 알려줍니다.

 


Pytype:

Pytype은 또 다른 Python 정적 타입 검사 도구.

어노테이션이 없어도 추론을 기반으로 동작함.

 

예를들어 아래와 같이 어노테이션이 없는 코드에 대해서 pytype을 수행하면

def multiply_numbers(a, b):
    return a * b

result = multiply_numbers(5, '3')
print(result)

아래처럼 런타임전 경고를 확인할 수 있음(런타임에서는  Python은 문자열 '3'을 5번 반복하여 '33333'을 생성하고 오류로 판단하지 않음)

$ pytype example.py
Computing dependencies
Analyzing 1 sources with 0 local dependencies
ninja: Entering directory `/Users/user/.pytype'
[1/1] check example
FAILED: /Users/user/.pytype/pyi/example.pyi 
pytype-single --imports_info /Users/user/.pytype/imports/example.imports --module-name example -V 3.8 -o /Users/user/.pytype/pyi/example.pyi --analyze-annotated --nofail --quick /Users/user/example.py
File "/Users/user/example.py", line 4, in <module>: unsupported operand type(s) for *: 'int' and 'str' [unsupported-operands]
  Function multiply_numbers was called with the wrong arguments (1:15)

For more details, see https://google.github.io/pytype/errors.html#unsupported-operands.
ninja: build stopped: subcommand failed.

 

Mypy vs Pytype

Mypy

Mypy는 Dropbox에서 개발되었으며, Python의 첫 번째 정적 타입 검사 시스템으로 간주됩니다.

이 도구는 2012년부터 개발이 시작되었으며, 아직도 활발하게 개발이 진행되고 있습니다​.
Mypy는 standalone으로 실행되거나, 커맨드 라인이나 편집기 또는 IDE의 linter 통합의 일부로 작동할 수 있습니다.
Mypy는 타입 어노테이션을 포함하지 않은 코드에 대해 대부분의 코드 검사를 수행하지 않으며, 이는 점진적으로 코드 기반을 주석 처리하고 있는 경우 Mypy가 시간을 낭비하지 않도록 하기 위함입니다.


Pytype

Pytype는 Google에서 개발되었으며, Mypy와 달리 타입 설명자 대신 추론을 사용합니다. 즉, Pytype는 코드 흐름을 분석하여 타입을 결정하려고 시도하며, 타입 어노테이션에 엄격하게 의존하지 않습니다​1.
Pytype는 가능한 한 관대하게 행동하며, 런타임에서 작동하고 어떤 어노테이션도 모순되지 않는 연산이 있으면 Pytype는 그것에 대해 불평하지 않습니다.
pytype의 reveal_type(expr)을 사용해서 런타임 전에 type()과 비슷한 기능을 활용하여 디버깅/개발 가능

 

 

반응형

'Programming > Python' 카테고리의 다른 글

pyenv를 통한 python버전 변경  (0) 2023.12.17
venv  (0) 2023.12.17
python 한글 스트링 인코딩 핸들링  (0) 2021.11.30
Python GUI Programming(Tkinter)  (1) 2021.01.02
파이선환경 그리고 requirements.txt  (1) 2020.09.20

TypeScript vs ECMAScript

ECMAScript (ES):

ECMAScript는 JavaScript 언어의 표준 사양입니다.
ECMAScript는 JavaScript의 기본 구조와 규칙을 설정하며, JavaScript 엔진이 이 표준을 따라 구현되어야 합니다.
ECMA International이라는 표준화 기구가 관리하며, 시간이 지남에 따라 여러 버전의 ECMAScript 사양이 발표되었습니다 (예: ES3, ES5, ES6/ES2015 등).


TypeScript

TypeScript는 Microsoft에서 개발한 JavaScript의 상위 집합(superset) 언어입니다.
TypeScript는 JavaScript의 모든 기능을 포함하며, 그 위에 정적 타입 체크와 인터페이스, 제네릭, 데코레이터 등과 같은 추가 기능을 제공합니다.
정적 타입 체크는 코드의 오류를 런타임 전에 찾아내고, 코드의 가독성과 유지 관리성을 향상시킵니다.
TypeScript는 개발 시간에 TypeScript 코드를 JavaScript 코드로 변환(트랜스파일)하여 실행할 수 있도록 합니다. 이는 TypeScript 컴파일러를 사용하여 수행됩니다.


비교:

타입 시스템: TypeScript는 정적 타입 시스템을 제공하는 반면, ECMAScript(JavaScript)는 동적 타입 시스템을 사용합니다.
추가 기능: TypeScript는 ECMAScript의 기능에 추가적인 기능을 제공하여, 큰 프로젝트의 관리와 유지 보수를 돕습니다.
호환성: TypeScript는 JavaScript와 완벽하게 호환되며, TypeScript 코드는 JavaScript 코드로 변환될 수 있습니다.
표준화: ECMAScript는 국제 표준이며, TypeScript는 Microsoft에 의해 관리되는 오픈 소스 프로젝트입니다.

 

코드 샘플

// javascript
function add(a, b) {
    return a + b;
}

const result = add(5, '10');  // Output: '510' (문자열 연결)


//typescript
function add(a: number, b: number): number {
    return a + b;
}

const result = add(5, '10');  // Type Error: Argument of type 'string' is not assignable to parameter of type 'number'.

위의 TypeScript 코드에서 add 함수는 두 개의 숫자 타입 매개변수를 받아야 하며, 숫자 타입의 값을 반환해야 한다는 것을 명시적으로 지정합니다. 따라서, 문자열을 매개변수로 전달하려고 하면 컴파일 타임에 타입 에러가 발생합니다.

이 예제는 TypeScript의 정적 타이핑이 코드의 안정성을 어떻게 향상시키는지 보여줍니다. TypeScript는 개발자에게 잠재적인 문제를 미리 알려주어, 런타임 에러를 줄이고 코드의 품질을 향상시킵니다.

 

반응형

'Programming > frontend' 카테고리의 다른 글

local storage/session storage  (0) 2023.10.07

도입배경

 

2000년대 초반에는 웹 개발에서 주로 쿠키를 사용하여 클라이언트 측 데이터를 저장하곤 했습니다. 그러나 쿠키는 몇 가지 제약 사항과 문제점이 있었습니다. 예를 들어, 쿠키는 작은 용량의 데이터만 저장할 수 있고, 모든 HTTP 요청에 자동으로 포함되어 네트워크 부하를 초래할 수 있습니다.

이러한 문제를 해결하기 위해 HTML5 명세의 일부로 Local Storage와 Session Storage가 도입되었습니다. 이 기술은 2009년에 W3C에 의해 표준화되었고, 대부분의 현대 웹 브라우저에서 지원됩니다

 

.

Local Storage와 Session Storage의 주요 특징:


클라이언트 측 저장: 이들은 클라이언트 측에서만 접근 가능하며, 서버로 전송되지 않습니다. 이로 인해 네트워크 부하가 줄어듭니다.

용량: 일반적으로 5~10MB의 데이터를 저장할 수 있어, 쿠키보다 훨씬 더 많은 정보를 저장할 수 있습니다.

API: JavaScript를 통해 간단하게 데이터를 저장하고 검색할 수 있는 API가 제공됩니다.

Session Storage는 탭이나 창이 닫힐 때까지 데이터를 유지하며, Local Storage는 영구적으로 데이터를 저장합니다.

Local Storage는 도메인, 프로토콜, 그리고 포트까지 모두 일치해야만 데이터에 접근할 수 있기 때문에, 포트가 다른 두 서비스에서는 Local Storage를 공유할 수 없습니다. 

 

 

코드 샘플

// Local Storage에 정보 저장
localStorage.setItem('isLoggedIn', 'true');

//Local Storage에서 정보 불러오기
const storedIsLoggedIn = localStorage.getItem('isLoggedIn');

 

브라우저에서 확인

크롬에서 F12를 누르고 애플리케이션탭 > 왼쪽탭에서 로컬 스토리지를 보면 다음처럼 내용 확인이 가능하다.

반응형

'Programming > frontend' 카테고리의 다른 글

TypeScript  (0) 2023.10.07

생성 패턴 (Creational Patterns)
생성 패턴은 객체 생성과 관련된 문제를 해결합니다.

싱글턴 패턴 (Singleton Pattern): 클래스의 인스턴스가 하나만 생성되도록 보장.
팩토리 메서드 패턴 (Factory Method Pattern): 객체 생성을 서브클래스에 위임.
빌더 패턴 (Builder Pattern): 복잡한 객체를 단계별로 생성합니다.

  • 생성자의 인수가 너무 길고 복잡할때 이를 보완.
  • method chaining 과 함께 사용


프로토타입 패턴 (Prototype Pattern): 기존 객체를 복제하여 새 객체를 생성합니다.

  • python deepcopy()


구조 패턴 (Structural Patterns)

구조 패턴은 클래스나 객체의 구성을 단순화하고 확장성을 높입니다.

어댑터 패턴 (Adapter Pattern): 호환되지 않는 인터페이스를 변환하여 함께 작동하게 합니다.

  • 바꾸기 힘든 레거시를 품은 새로운 클래스 만들어서 기존 구조와 통합


브리지 패턴 (Bridge Pattern): 구현부에서 추상화를 분리하여 독립적으로 변화할 수 있게 합니다.

  • 잘 안쓰는듯? abstract 와 interface 사이에 다르를 놓는것 같은데..


컴포지트 패턴 (Composite Pattern): 객체들을 트리 구조로 구성하여 부분-전체 계층을 표현합니다.

  • 단일요소와 집합요소를 모두 같은 부모를 상속 받도록 해서 복잡한 트리구조도 단일 개체로 표현가능
  • Folder와 File이 모두 Unit이라는 부모를 상속받도록 하는 것 생각


데코레이터 패턴 (Decorator Pattern): 객체에 동적으로 새로운 책임을 추가합니다.
퍼사드 패턴 (Facade Pattern): 복잡한 시스템에 대한 간단한 인터페이스를 제공합니다.

  • 이건 그냥 복잡한 단계를 하나의 API로 묶어서 단순화 하는 개념.


플라이웨이트 패턴 (Flyweight Pattern): 공유를 통해 대량의 객체를 효율적으로 지원합니다.
프록시 패턴 (Proxy Pattern): 다른 객체에 대한 대리자나 플레이스홀더 역할을 합니다.

  • 준비시간이 오래걸리는 프린터 앞에 버퍼프린터 대리자로 모아서 처리하는거 생각하면 됨


행동 패턴 (Behavioral Patterns)
행동 패턴은 객체 간의 책임과 알고리즘을 관리합니다.

책임 연쇄 패턴 (Chain of Responsibility Pattern): 요청을 객체의 체인을 통해 전달합니다.
명령 패턴 (Command Pattern): 요청을 객체로 캡슐화합니다.

  • 행동도 객체화 할수 있다는 것(포토샵 액션 리스트 생각)


해석자 패턴 (Interpreter Pattern): 문법 규칙을 클래스로 표현합니다.

  • 컴파일러 비슷한일 할때, 클래스로 처리


반복자 패턴 (Iterator Pattern): 컬렉션의 요소를 순회하는 방법을 제공합니다.
중재자 패턴 (Mediator Pattern): 객체 간의 상호작용을 캡슐화하여 객체들이 직접 참조하지 않도록 합니다.
메멘토 패턴 (Memento Pattern): 객체의 상태를 캡쳐하고 복원합니다.
감시자 패턴 (Observer Pattern): 객체 간의 일대다 의존성을 정의합니다.

  • 구독하면 알림보내는거 생각하면됨. 이벤트일 필요없고 콜백도 가능


상태 패턴 (State Pattern): 객체의 내부 상태에 따라 행동을 변경합니다.

  • 상태를 객체로 표현하여, 조건문처리를 단순화 한다.


전략 패턴 (Strategy Pattern): 알고리즘을 캡슐화하여 상호 교체 가능하게 합니다.

  • 암것도 아님. 추상화를 통해 sum()등의 행동을 갈아 끼울 수 있다는 것.


템플릿 메서드 패턴 (Template Method Pattern): 알고리즘의 구조를 정의합니다.
방문자 패턴 (Visitor Pattern): 객체 구조에 새로운 연산을 추가합니다.

  • 데이터와 행동을 클래스 분리. 이렇게 하면 행동의 추가 확장이 편해짐(sum, avg등)
반응형

'Programming' 카테고리의 다른 글

go언어(golang)  (0) 2025.05.25
window에서 vscode로 원격 linux에 대한 ssh 개발환경 설정하기  (1) 2024.07.20
yaml  (0) 2024.03.02
라즈베리파이 초기 세팅  (1) 2023.01.20
STL lower_bound, upper_bound  (0) 2020.04.12

+ Recent posts