Хобрук: Ваш путь к мастерству в программировании

Spring-Data Neo4J @Repository @Query не работает

Ниже приведена трассировка стека, сгенерированная после создания исключения:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-09-18 10:45:31.825 ERROR 16349 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'repositoryPropertyReferenceController' defined in URL [jar:file:/home/sean/.gradle/caches/modules-2/files-2.1/org.springframework.data/spring-data-rest-webmvc/3.1.0.RC2/11b3ab6953b3a0f6a433e48be1b0d2db668602c5/spring-data-rest-webmvc-3.1.0.RC2.jar!/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'repositories' defined in class path resource [org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.repository.support.Repositories]: Factory method 'repositories' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'matchRepo': Invocation of init method failed; nested exception is java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:766) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:217) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1302) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1154) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:829) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:865) ~[spring-context-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548) ~[spring-context-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:797) [spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:421) [spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:340) [spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) [spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1279) [spring-boot-2.1.0.M3.jar:2.1.0.M3]
    at io.sciro.leaderdata.LeaderDataApp.main(LeaderDataApp.java:22) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'repositories' defined in class path resource [org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.repository.support.Repositories]: Factory method 'repositories' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'matchRepo': Invocation of init method failed; nested exception is java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:624) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:455) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1282) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1126) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:290) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1222) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1149) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:854) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:757) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    ... 19 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.repository.support.Repositories]: Factory method 'repositories' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'matchRepo': Invocation of init method failed; nested exception is java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:619) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    ... 33 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'matchRepo': Invocation of init method failed; nested exception is java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1743) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:576) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1091) ~[spring-context-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.data.repository.support.Repositories.cacheRepositoryFactory(Repositories.java:97) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.repository.support.Repositories.populateRepositoryFactoryInformation(Repositories.java:90) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.repository.support.Repositories.<init>(Repositories.java:83) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.repositories(RepositoryRestMvcConfiguration.java:242) ~[spring-data-rest-webmvc-3.1.0.RC2.jar:3.1.0.RC2]
    at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration$$EnhancerBySpringCGLIB$$bb148bd0.CGLIB$repositories$5(<generated>) ~[spring-data-rest-webmvc-3.1.0.RC2.jar:3.1.0.RC2]
    at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration$$EnhancerBySpringCGLIB$$bb148bd0$$FastClassBySpringCGLIB$$e615e97.invoke(<generated>) ~[spring-data-rest-webmvc-3.1.0.RC2.jar:3.1.0.RC2]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration$$EnhancerBySpringCGLIB$$bb148bd0.repositories(<generated>) ~[spring-data-rest-webmvc-3.1.0.RC2.jar:3.1.0.RC2]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    ... 34 common frames omitted
Caused by: java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:545) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:324) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:297) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.util.Lazy.getNullable(Lazy.java:211) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.util.Lazy.get(Lazy.java:94) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:300) ~[spring-data-commons-2.1.0.RC2.jar:2.1.0.RC2]
    at org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean.afterPropertiesSet(Neo4jRepositoryFactoryBean.java:66) ~[spring-data-neo4j-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1802) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1739) ~[spring-beans-5.1.0.RC3.jar:5.1.0.RC3]
    ... 55 common frames omitted


Process finished with exit code 1

Мой репозиторий написан следующим образом:

package io.sciro.leaderdata.repo;

import io.sciro.leaderdata.domain.Match;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;

@Repository
@RepositoryRestResource(path = "match", collectionResourceRel = "match")
public interface MatchRepo extends Neo4jRepository<Match, Long> {

    //If I delete the line below, everything works fine.        
    Match findMatchByCodeName(String codeName);

    //If I delete the line below, everything works fine.
    Iterable<Match> findMatchesByCodeName(@Param("codeName")  String codeName);
}

Моя сущность/домен:

@NodeEntity
public class Match {

    @Id
    @GeneratedValue
    private Long id;
    private String codeName;
    private Long round;
    private String me;
    private String pc;
    private Character result;
    private Date timestamp;
    @LastModifiedDate
    private Date lastUpdated;
    @CreatedDate
    private Date created;

    //Getters & Setters
}

У меня есть следующий скрипт Gradle:

buildscript {
    ext {
        springBootVersion = '2.1.0.M3'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.sciro'
version = '0.1'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}


ext {
    springBootAdminVersion = '2.1.0-SNAPSHOT'
    springCloudVersion = 'Greenwich.BUILD-SNAPSHOT'
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-actuator')
    compile('org.springframework.boot:spring-boot-starter-data-neo4j')
    compile('org.springframework.data:spring-data-neo4j:5.0.9.RELEASE')
    compile('org.neo4j:neo4j-ogm-bolt-driver:3.1.1-RC1')
    compile('org.neo4j:neo4j-ogm-http-driver:3.1.1-RC1')
    compile('org.springframework.boot:spring-boot-autoconfigure')
    compile('org.springframework.boot:spring-boot-starter-data-rest')
//    compile('org.springframework.boot:spring-boot-starter-hateoas')
    compile('de.codecentric:spring-boot-admin-starter-client')
    compile('de.codecentric:spring-boot-admin-starter-server')
    compile('org.springframework.cloud:spring-cloud-starter-config')
    compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
    // https://mvnrepository.com/artifact/org.springframework.data/spring-data-commons-core

//    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
//    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard')
//    compile('org.springframework.data:spring-data-rest-hal-browser')
    compile('org.springframework.cloud:spring-cloud-config-client')
//    runtime('org.springframework.boot:spring-boot-devtools')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-parent
    compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-parent', version: 'Finchley.RELEASE', ext: 'pom'

}

dependencyManagement {
    imports {
        mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

Я что-то упускаю? Я сейчас как-то застрял в этом вопросе. Альтернативное решение, которое сработало для меня, состояло в том, чтобы удалить эти две строки кода из интерфейса MatchRepo:

    //If I delete the line below, everything works fine.        
    Match findMatchByCodeName(String codeName);

    //If I delete the line below, everything works fine.
    Iterable<Match> findMatchesByCodeName(@Param("codeName")  String codeName);

Я просто использую метод findAll(), расширенный из расширенного интерфейса Neo4jRepository, а затем использую поток java-8 для фильтрации объекта Match, который соответствует codeName. Уродливое решение на самом деле. Если кто-то знает, как правильно решить эту проблему, пожалуйста, помогите.


  • вам нужно добавить запросы с @Query для каждого метода репо 18.09.2018
  • @benjaminc Я тоже пробовал, проблемы все еще есть. :-( Разве у него нет чего-то, чтобы сделать некоторые отсутствующие/конфликтующие зависимости или версию Spring-Cloud? В примерах и документации Neo4j Spring Data они также сделали то же самое. 18.09.2018
  • вы добавили @EnableNeo4jRepositories где-то в классе конфигурации 18.09.2018
  • @benjaminc Да, я добавил эту аннотацию в класс конфигурации. 20.09.2018

Ответы:


1

Изменить: похоже, вы используете несовместимые версии Spring-Data-Commons и Spring Data Neo4j. Вы перезаписываете версию SDN на 5.0.9, но не на версию Release Train.

В любом случае рекомендуемая схема именования для имен производных методов выглядит примерно так:

package io.sciro.leaderdata.repo;

import io.sciro.leaderdata.domain.Match;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Repository;

@Repository
@RepositoryRestResource(path = "match", collectionResourceRel = "match")
public interface MatchRepo extends Neo4jRepository<Match, Long> {

    Match findOneByCodeName(String codeName);

    Iterable<Match> findAllByCodeName(@Param("codeName")  String codeName);
}
19.09.2018
  • Спасибо, что указали на этот факт. это действительно было несоответствие версии в зависимостях. 19.11.2018
  • Новые материалы

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..

    Расистский и сексистский робот, обученный в Интернете
    Его ИИ основан на предвзятых данных, которые создают предрассудки. Он словно переходит из одного эпизода в другой из серии Черное зеркало , а вместо этого представляет собой хронику..

    Управление состоянием в микрофронтендах
    Стратегии бесперебойного сотрудничества Микро-фронтенды — это быстро растущая тенденция в сфере фронтенда, гарантирующая, что удовольствие не ограничивается исключительно бэкэнд-системами..

    Декларативное и функциональное программирование в стиле LINQ с использованием JavaScript с использованием каррирования и генератора ...
    LINQ - одна из лучших функций C #, которая обеспечивает элегантный способ написания кода декларативного и функционального стиля, который легко читать и понимать. Благодаря таким функциям ES6,..

    Структуры данных в C ++ - Часть 1
    Реализация общих структур данных в C ++ C ++ - это расширение языка программирования C, которое поддерживает создание классов, поэтому оно известно как C с классами . Он используется для..

    Как я опубликовал свое первое приложение в App Store в 13 лет
    Как все началось Все началось три года назад летом после моего четвертого класса в начальной школе. Для меня, четвертого класса, лето кажется бесконечным, пока оно не закончится, и мой отец..