å æ¥ãLocalStack 2.0ããªãªã¼ã¹ãããã®ã§ãSpring Bootã¢ããªã±ã¼ã·ã§ã³ã§å©ç¨ãã¦ã¿ã¾ãã LocalStack 1.0ã¨2.0ã®éããChatGPTã«èãã¦ã¿ã¾ããã
LocalStack 1.0ã¨LocalStack 2.0ã¯ãAWSã¯ã©ã¦ããªã½ã¼ã¹ããã¼ã«ã«ç°å¢ã§ã·ãã¥ã¬ã¼ãããããã«ä½¿ç¨ããããªã¼ãã³ã½ã¼ã¹ããã¸ã§ã¯ãã§ããããã«ãããéçºè ã¯ãªãã©ã¤ã³ã§AWSãµã¼ãã¹ããã¹ãã§ãã¾ããLocalStack 2.0ã¯ã1.0ã«æ¯ã¹ã¦ããã¤ãã®éè¦ãªæ¹åç¹ãããã¾ãã ããã©ã¼ãã³ã¹ã¨å®å®æ§ï¼LocalStack 2.0ã¯ãããé«éã§å®å®ããããã©ã¼ãã³ã¹ãæå¾ ãããããã«è¨è¨ããã¦ãã¾ãã1.0ã«åå¨ããããã¤ãã®ãã°ãåé¡ãä¿®æ£ããã¦ãã¾ãã ãµãã¼ãããããµã¼ãã¹ï¼LocalStack 2.0ã§ã¯ãæ°ããAWSãµã¼ãã¹ã¨æ©è½ã®ãµãã¼ãã追å ããã¦ãã¾ããããã«ãããéçºè ã¯ææ°ã®AWSæ©è½ããã¼ã«ã«ã§å©ç¨ã§ããããã«ãªãã¾ãã ããã¸ã§ã¯ãæ§é ã¨ã¢ã¼ããã¯ãã£ï¼LocalStack 2.0ã§ã¯ãããã¸ã§ã¯ãã®æ§é ã¨ã¢ã¼ããã¯ãã£ãåè¨è¨ãããããç¶æ管çã容æã«ãªããæ¡å¼µæ§ãåä¸ãã¦ãã¾ãã ããã»ã¹åé¢ï¼LocalStack 1.0ã§ã¯ããã¹ã¦ã®ãµã¼ãã¹ã1ã¤ã®ããã»ã¹å ã§å®è¡ããã¦ãã¾ããããLocalStack 2.0ã§ã¯ãåãµã¼ãã¹ãç¬èªã®ããã»ã¹å ã§å®è¡ãããããã«ãªãã¾ãããããã«ããããµã¼ãã¹éã®ä¾åé¢ä¿ãåé¡ã軽æ¸ãããå®å®æ§ãåä¸ãã¦ãã¾ãã ã³ã³ãã£ã®ã¥ã¬ã¼ã·ã§ã³ã¨ãããã¤ã¡ã³ãï¼LocalStack 2.0ã§ã¯ãè¨å®ã¨ãããã¤ã¡ã³ãã®ããã»ã¹ãæ¹åããããã使ãããããªã£ã¦ãã¾ãã ã³ãã¥ããã£ãµãã¼ãï¼LocalStack 2.0ã¯ã1.0ã¨æ¯ã¹ã¦ã¢ã¯ãã£ããªéçºã¨ãµãã¼ããç¶ãã¦ãããéçºè ãæ°ããæ©è½ããã°ä¿®æ£ãå©ç¨ã§ããããã«ãªã£ã¦ãã¾ãã ãããã®æ¹åç¹ã«ãããLocalStack 2.0ã¯1.0ãããé«éã§å®å®ããããå¤ãã®AWSãµã¼ãã¹ã¨æ©è½ããµãã¼ãããéçºè ãAWSã¢ããªã±ã¼ã·ã§ã³ããã¼ã«ã«ç°å¢ã§ãã¹ãããéã«åªããé¸æè¢ã¨ãªãã¾ãã
LocalStackã®æéä½ç³»ã¯ãç¡æã¨ææãããã¾ããææçã§ããå©ç¨ã§ããªãAWSã¯ã©ã¦ããªã½ã¼ã¹ãããã¾ãã詳ããã¯ãAWS Service Feature Coverageã«æ¸ãã¦ããã¾ãã
LocalStackãå©ç¨ããSpring Bootã¢ããªã±ã¼ã·ã§ã³
æ¬è¨äºã®ç°å¢ - Java 17 - Spring Boot 3.0.6 - AWS SDK for Java 2.20.58 - LocalStack 2.0.2 - Testcontainers 1.18.0
Dockerä¸ã§LocalStackãèµ·åãã
Dockerä¸ã§LocalStackãèµ·åããããã«ã次ã®docker-compose.ymlãç¨æãã¾ããã
version: "3.9" services: localstack: image: localstack/localstack:2.0.2 ports: - "127.0.0.1:4566:4566" environment: # LocalStackã®ãã°ãåºåãããã©ãããæå® - DEBUG=1 # Docker Composeã§èµ·åãããã³ã³ãããDockerãã¼ã¢ã³ã¨éä¿¡ããããã«ä½¿ç¨ããDockerãã¹ãã®ã¢ãã¬ã¹ãæå® - DOCKER_HOST=unix:///var/run/docker.sock
èµ·åãã¾ãã
$ docker-compose up -d [+] Running 1/1 â Container localstack2-java-localstack-1 Started
AWSç°å¢ã®ã»ããã¢ãã
AWS CLIã®ã³ãã³ãã§ãLocalStackå ã®å ¨ãµã¼ãã¹ã«ã¢ã¯ã»ã¹ãããã¨ãã§ãã¾ããã¾ããLocalStackã«ç¹åããLocalStack AWS CLIã¨ãããã®ãããã¾ãã
CLIã®ã¤ã³ã¹ãã¼ã«
AWS CLIãLocalStack AWS CLIã®ã©ã¡ãããã¤ã³ã¹ãã¼ã«ãã¦ãã ããã
# AWS CLI $ pip install awscli # LocalStack AWS CLI $ pip install awscli-local
ã¯ã¬ãã³ã·ã£ã«ã®è¨å®
ã¯ã¬ãã³ã·ã£ã«ã¨ããã©ã«ããªã¼ã¸ã§ã³ã®ç°å¢å¤æ°ãè¨å®ãã¦ãã ããã
$ export AWS_ACCESS_KEY_ID="test" $ export AWS_SECRET_ACCESS_KEY="test" $ export AWS_DEFAULT_REGION="ap-northeast-1"
ãã®ã¯ã¬ãã³ã·ã£ã«ã¯ããã¼ãªã®ã§ãdirenvãå©ç¨ãã¦ããã®ãªãã¸ããªãã£ã¬ã¯ããªã®ã¿ç°å¢å¤æ°ãé©ç¨ãããããã«ãã¾ããã
export AWS_ACCESS_KEY_ID="test" export AWS_SECRET_ACCESS_KEY="test" export AWS_DEFAULT_REGION="ap-northeast-1"
åä½ç¢ºèª
AWS CLIãLocalStack AWS CLIã®ã³ãã³ããå©ãã¦åä½ç¢ºèªããã¾ãã
$ aws --endpoint-url=http://localhost:4566 kinesis list-streams "StreamNames": [] } $ awslocal kinesis list-streams "StreamNames": [] }
ããã§LocalStackãå©ç¨ããç°å¢ã¯æ´ãã¾ããã
Spring Boot ã¢ããªã±ã¼ã·ã§ã³ã®å®è£
ãµã³ãã«ã¨ãã¦ãAmazon SQSãå©ç¨ãã¦ãã¥ã¼ã«ã¡ãã»ã¼ã¸ãæ ¼ç´ããã³ãã³ãã©ã¤ã³ã¢ããªã±ã¼ã·ã§ã³ãå®è£ ãã¾ãã ç´°ããå®è£ ã¯ãGitHubãã覧ãã ããã
SqsClientãBeanç»é²ãã¾ããä»åãLocalStackã4566ãã¼ãã§èµ·åãã¦ããã®ã§ã¨ã³ããã¤ã³ããæ¸ãæããå¿ è¦ãããã¾ãã
@Configuration(proxyBeanMethods = false) public class SqsConfig { @Bean public SqsClient sqsClient(SqsProperties properties) { var builder = SqsClient.builder() .region(Region.AP_NORTHEAST_1) .endpointOverride(properties.endpointUrl()); if (properties.endpointUrl() != null) { // LocalStackã®ã¨ã³ããã¤ã³ãã«æ¸ãæãã builder.endpointOverride(properties.endpointUrl()); } return builder.build(); } }
SQSã®ãã¥ã¼ã«ã¡ãã»ã¼ã¸ãæ ¼ç´ããå¦çãå®è£ ã
@Component public class SqsQueueMessageSender implements ApplicationRunner { static final String DEFAULT_MESSAGE = "test-message"; private final SqsClient sqsClient; private final SqsProperties sqsProperties; public SqsQueueMessageSender(SqsClient sqsClient, SqsProperties sqsProperties) { this.sqsClient = sqsClient; this.sqsProperties = sqsProperties; } @Override public void run(ApplicationArguments args) { var queueUrl = sqsClient.getQueueUrl(builder -> builder.queueName(sqsProperties.queueName())) .queueUrl(); sqsClient.sendMessage(builder -> builder.queueUrl(queueUrl).messageBody(DEFAULT_MESSAGE)); } }
SQSãã¥ã¼ã®ä½æ
AWS CLIãLocalStack AWS CLIã®ã³ãã³ããå©ãã¦ãã¥ã¼ãä½æãã¾ãã
$ aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name sample-queue $ awslocal sqs create-queue --queue-name sample-queue { "QueueUrl": "http://localhost:4566/000000000000/sample-queue" }
ã¢ããªã±ã¼ã·ã§ã³ã®å®è¡
Spring Bootã¢ããªã±ã¼ã·ã§ã³ãèµ·åãã¦ããã¥ã¼ã«ã¡ãã»ã¼ã¸ãæ ¼ç´ãã¾ãã
./gradlew clean bootRun --args='--spring.profiles.active=local' > Task :bootRun . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.6) 2023-05-05T13:40:25.871+09:00 INFO 21202 --- [ main] c.b1a9idps.localstack2java.Application : Starting Application using Java 17.0.6 with PID 21202 (/Users/ryosuke/workspace/localstack2-java/build/classes/java/main started by ryosuke in /Users/ryosuke/workspace/localstack2-java) 2023-05-05T13:40:25.873+09:00 INFO 21202 --- [ main] c.b1a9idps.localstack2java.Application : The following 1 profile is active: "local" 2023-05-05T13:40:26.617+09:00 INFO 21202 --- [ main] c.b1a9idps.localstack2java.Application : Started Application in 1.145 seconds (process running for 1.402) BUILD SUCCESSFUL in 2s 5 actionable tasks: 5 executed
AWS CLIãLocalStack AWS CLIã®ã³ãã³ããæ ¼ç´ããã¡ãã»ã¼ã¸ãåä¿¡ã§ãããã¨ã確èªãã¾ãã
$ aws --endpoint-url=http://localhost:4566 sqs receive-message \ --queue-url=http://queue.localhost.localstack.cloud:4566/000000000000/sample-queue $ awslocal sqs receive-message \ --queue-url=http://queue.localhost.localstack.cloud:4566/000000000000/sample-queue
ç¡äºã«LocalStackãå©ç¨ãã¦AWSã¯ã©ã¦ããªã½ã¼ã¹ããã¼ã«ã«ç°å¢ã§ã·ãã¥ã¬ã¼ããããã¨ãã§ãã¾ããã
Testcontainersãå©ç¨ãããã¹ãã³ã¼ã
LocalStackããµãã¼ããã¦ããTestcontainersãå©ç¨ãã¦ãã¹ãã³ã¼ããæ¸ãã¾ãã SqsClientã®ã¯ã¬ãã³ã·ã£ã«çãTestcontainersç¨ã®ãã®ãå©ç¨ãããã¨ã§ããã¹ãã³ã¼ããæ¸ããã¨ãã§ãã¾ãã
@ExtendWith(SpringExtension.class) @ActiveProfiles("unittest") @ContextConfiguration(classes = {TestConfig.class}, initializers = ConfigDataApplicationContextInitializer.class) @Testcontainers class SqsQueueMessageSenderTest extends AbstractContainerBaseTest { @Container final LocalStackContainer localStack = new LocalStackContainer(LOCALSTACK_IMAGE_NAME) .withServices(LocalStackContainer.Service.SQS); SqsQueueMessageSender sqsQueueMessageSender; SqsClient sqsClient; @Autowired SqsProperties sqsProperties; @BeforeEach void beforeEach() { sqsClient = SqsClient.builder() .endpointOverride(localStack.getEndpointOverride(LocalStackContainer.Service.SQS)) .credentialsProvider( StaticCredentialsProvider.create( AwsBasicCredentials.create(localStack.getAccessKey(), localStack.getSecretKey()) ) ) .region(Region.of(localStack.getRegion())) .build(); sqsClient.createQueue(builder -> builder.queueName(sqsProperties.queueName())); sqsQueueMessageSender = new SqsQueueMessageSender(sqsClient, sqsProperties); } @Test void sendMessage() { sqsQueueMessageSender.run(null); var queueUrl = sqsClient.getQueueUrl(builder -> builder.queueName(sqsProperties.queueName())) .queueUrl(); var actual = sqsClient.receiveMessage(builder -> builder .queueUrl(queueUrl) .maxNumberOfMessages(10)); Assertions.assertThat(actual.messages()) .hasSize(1) .extracting(Message::body) .containsExactly(DEFAULT_MESSAGE); } }