{"id":20564421,"url":"https://github.com/tarantool/spring-petclinic-tarantool","last_synced_at":"2025-04-14T15:12:56.181Z","repository":{"id":42434976,"uuid":"346465730","full_name":"tarantool/spring-petclinic-tarantool","owner":"tarantool","description":"Simple example how to use cartridge spring data ","archived":false,"fork":false,"pushed_at":"2023-01-19T12:33:28.000Z","size":6937,"stargazers_count":5,"open_issues_count":2,"forks_count":1,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-14T15:12:46.219Z","etag":null,"topics":["java","spring","tarantool"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tarantool.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-10T19:17:04.000Z","updated_at":"2025-01-27T11:03:51.000Z","dependencies_parsed_at":"2023-01-19T13:45:26.341Z","dependency_job_id":null,"html_url":"https://github.com/tarantool/spring-petclinic-tarantool","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fspring-petclinic-tarantool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fspring-petclinic-tarantool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fspring-petclinic-tarantool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fspring-petclinic-tarantool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tarantool","download_url":"https://codeload.github.com/tarantool/spring-petclinic-tarantool/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248904637,"owners_count":21180835,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","spring","tarantool"],"created_at":"2024-11-16T04:26:29.799Z","updated_at":"2025-04-14T15:12:56.144Z","avatar_url":"https://github.com/tarantool.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tarantool Spring PetClinic Sample Application\n\n## Understanding the original Spring Petclinic application with a few diagrams\n\u003ca href=\"https://speakerdeck.com/michaelisvy/spring-petclinic-sample-application\"\u003eSee the presentation here\u003c/a\u003e\n\n## Running petclinic locally\nFirst you need to [install](https://www.tarantool.io/en/download/os-installation/) tarantool on the system. Well, then you need to start the tarantool cluster:\n```\ngit clone git@github.com:tarantool/spring-petclinic-tarantool.git\ncd spring-petclinic-tarantool/cluster\ncartridge build                                                                         # Install modules\ncartridge start -d                                                                      # Start cluster in deamonize mode\ncartridge replicasets setup --bootstrap-vshard                                          # Setup replica sets described in a file replicasets.yml\ncurl -X POST http://localhost:8081/migrations/up                                        # Run cluster-wide migrations for your data\necho \"require('data')\" | tarantoolctl connect admin:secret-cluster-cookie@0.0.0.0:3301  # Fill the cluster with initial data by passing in router\n```\nPetclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/). You can build a jar file and run it from the command line:\n\n\n```\ncd ..\n./mvnw package\njava -jar target/*.jar\n```\n\nYou can then access petclinic here: http://localhost:8080/\n\n\u003cimg width=\"1042\" alt=\"petclinic-screenshot\" src=\"https://cloud.githubusercontent.com/assets/838318/19727082/2aee6d6c-9b8e-11e6-81fe-e889a5ddfded.png\"\u003e\n\nOr you can run it from Maven directly using the Spring Boot Maven plugin. If you do this it will pick up changes that you make in the project immediately (changes to Java source files require a compile as well - most people use an IDE for this):\n\n```\n./mvnw spring-boot:run\n```\n\n**Version restrictions**  \nFor this example, version restrictions were found:\nThe `cartridge replicasets setup` command works only when `cartridge-cli \u003e= 2.5.0`.   \nIn the future, the example will be improved to work without limiting versions.\n\n## About tarantool cluster\n\n\nTarantool is Reliable NoSQL DBMS.\nThis example is run using the [cartridge framework](https://www.tarantool.io/en/cartridge/),\nwhich is a powerful orchestrator for Tarantool.\nData sharding is performed on the principle of virtual buckets using the [vshard module](https://github.com/tarantool/vshard).\nTherefore, to begin with, we must install all the necessary modules, they are described in the `testserver-scm-1.rockspec`\nfile using the command `tarantoolctl rocks make`\n\n---\nAll the code with the cluster logic is in the cluster folder. Using the `cartridge start -d` command, the cartridge framework starts several tarantool instances, the configuration of which is described in the instances.yml file:\n```yaml\n---\ntestserver.router:\n  advertise_uri: localhost:3301\n  http_port: 8081\n\ntestserver.s1-master:\n  advertise_uri: localhost:3302\n  http_port: 8082\n\ntestserver.s1-replica:\n  advertise_uri: localhost:3303\n  http_port: 8083\n\ntestserver.s2-master:\n  advertise_uri: localhost:3304\n  http_port: 8084\n\ntestserver.s2-replica:\n  advertise_uri: localhost:3305\n  http_port: 8085\n\ntestserver-stateboard:\n  listen: localhost:3310\n  password: passwd\n```\n\nIt follows that we are launching 6 tarantool instances.\n\n---\nNext, we create a topology from these instances using the command:\n`cartridge replicasets setup --bootstrap-vshard`  \nThe cluster configuration is located in the replicasets.yml file:\n```yaml\nr1:\n  instances:\n    - router\n  roles:\n    - vshard-router\n    - crud-router\n    - app.roles.api_router\n  all_rw: false\ns1:\n  instances:\n    - s1-master\n    - s1-replica\n  roles:\n    - vshard-storage\n    - crud-storage\n    - app.roles.api_storage\n  weight: 1\n  all_rw: false\n  vshard_group: default\ns2:\n  instances:\n    - s2-master\n    - s2-replica\n  roles:\n    - vshard-storage\n    - crud-storage\n    - app.roles.api_storage\n  weight: 1\n  all_rw: false\n  vshard_group: default\n\n```\n\n\n![](src/main/resources/static/resources/images/topology.png)\n\n---\n\nNext, using the [migration module ](https://github.com/tarantool/migrations) can run cluster-wide migrations for your data\n`curl -X POST http://localhost:8081/migrations/up`\n\nand fill in the data using a tarantool router connection:\n`echo \"require('data')\" | tarantoolctl connect admin:secret-cluster-cookie@0.0.0.0:3301`\n``\n\n## Database configuration\n\nThis default configuration assumes the use of [tarantool noSQL database](https://www.tarantool.io/). Tarantool and spring work using a special module for [cartridge-springdata](https://github.com/tarantool/cartridge-springdata). Therefore, the database configuration is somewhat different from the default h2.\n\n### Data Model diagram\nLet's look at the data model:\n![](src/main/resources/static/resources/images/schema.png)  \nHere we can immediately see that there are no familiar relationships using foreign keys, and the datatype of the primary keys is uuid.\nTo keep data normalization, data splitting and joining are being did in a special way:\n\n1. We use **uuid** as it is much faster than using any auto-increment on the storage cluster.\n   UUID can be generated both on the client and on the side of tarantool application scripts.\n2. **Join** happens predominantly on storages, if possible, and is aggregated using map reduce.\n3. Fields where the data type is indicated with a question mark means that this field can be **nullable**, this is done so that we can return data nested using custom joins.\n4. Since there are no foreign keys, **secondary indexes** were added to quickly fetch data. In the diagram, they are indicated by a graph tree.\n\n\n### Data nesting and joining\nYou can nest data from one space into another. To do this, you need to place an empty field in space_object: format. When transferring data from the database to the client, add data to this stub.\nE.g.:\n```lua\nowners:format({\n    { name = \"id\", type = \"uuid\" },\n    { name = \"first_name\", type = \"string\" },\n    { name = \"last_name\", type = \"string\" },\n    { name = \"address\", type = \"string\" },\n    { name = \"city\", type = \"string\" },\n    { name = \"telephone\", type = \"string\" },\n    { name = \"bucket_id\", type = \"unsigned\" },\n    { name = \"pets\", type = \"map\", is_nullable = true }\n})\n```\nWe will return the owner's data along with his pets. For embedding data, we have a pets field. NULL is stored in these field on storages.\n```lua\n-- OneToMany = Owners -\u003e Pets\nlocal function find_owner_by_id_without_pet_type(id)\n    local owner = crud.select(\"owners\", {{'=', 'id', id}})\n    local pets = crud.select(\"pets\", {{'=', 'owner_id', owner.rows[1][1]}})\n    pets = crud.unflatten_rows(pets.rows, pets.metadata)\n    owner.rows[1][8] = pets\n    return owner\nend\n```\n\nAs we can see, the data join occurs using the [crud](https://github.com/tarantool/crud) module, which allows us to make quieries to the cluster. If we do not have enough crud functionality, then we can use cartridge.pool.map_call. Just to demonstrate this, let's see how the **many to many** case is implemented:\n\nThe join of two tables vets and vet_specialties occurs on the storages, the result is returned to the router, and on the router, data from the 3rd table is pulled up afterwards:\n\nstorage:\n```lua\nlocal function get_vets_with_specialties_id()\n    local vets = {}\n    for _, vet in box.space.vets:pairs() do\n        local specialties_ids = {}\n        local specialties = box.space.vet_specialties.index.vet_id:select(vet[1])\n        for _, specialty in pairs(specialties) do\n            local specialty_id = specialty[2]\n            table.insert(specialties_ids, specialty_id)\n        end\n        vet = vet:totable()\n        table.insert(vet, specialties_ids)\n        table.insert(vets, vet)\n    end\n    return vets\nend\n```\n\nrouter:\n```lua\nlocal function get_vets_with_specialties()\n    local vets_list = {}\n    local vets_by_storage, err =\n    cartridge_pool.map_call('get_vets_with_specialties_id', {}, { uri_list = get_uriList() })\n    if err then\n        return nil, err\n    end\n    for _, vets in pairs(vets_by_storage) do\n        for _, vet in pairs(vets) do\n            local specialties = {}\n            -- one vet may has many specialties\n            for _, specialty_id in pairs(vet[#vet]) do\n                local specialty = crud.get(\"specialties\", specialty_id)\n                table.insert(specialties, crud.unflatten_rows(specialty.rows, specialty.metadata)[1])\n            end\n            -- replace specialty id by specialty name\n            vet[5] = specialties\n            table.insert(vets_list, vet)\n        end\n    end\n    return vets_list\nend\n```\n\n\n## Work from java client\n\nInteraction with Tarantool can be done by using Tarantool function binding via Query annotation indicated above or by using a standard CrudRepository functions (like findById, save, etc...).\n\n```java\n// Binding tarantool function\n@Query(function = \"find_owner_by_id\")\nOwner findOwnerById(UUID id);\n\n// Using default CrudRepository operations\nOwner save(Owner owner);\n```\n\nData mapping from tarantool spaces to java entities is implemented with @tuple and @field annotations. See [tarantool-springdata]((https://github.com/tarantool/cartridge-springdata)) module for details.\n\n```java\n@Tuple(\"visits\")\npublic class Visit extends BaseEntity {\n\n    @Field(name = \"visit_date\")\n    @DateTimeFormat(pattern = \"yyyy-MM-dd\")\n    private LocalDate date;\n\n    @NotEmpty\n    @Field(name = \"description\")\n    private String description;\n...\n}\n```\n\nData is obtained from cluster via custom functions lua functions or '[crud](https://github.com/tarantool/crud)' library that execute on 'router' instance. Here is an example of configuration to establish connection to tarantool router instance:\n```java\n@Configuration\n@EnableTarantoolRepositories(basePackageClasses = { VetRepository.class, OwnerRepository.class, PetRepository.class,\n        PetTypeRepository.class, VisitRepository.class })\npublic class TarantoolConfiguration extends AbstractTarantoolDataConfiguration {\n\n    // localhost\n    @Value(\"${tarantool.host}\")\n    protected String host;\n\n    // 3301 is our router\n    @Value(\"${tarantool.port}\")\n    protected int port;\n\n    // admin\n    @Value(\"${tarantool.username}\")\n    protected String username;\n\n    // secret-cluster-cookie\n    @Value(\"${tarantool.password}\")\n    protected String password;\n\n    @Override\n    protected void configureClientConfig(TarantoolClientConfig.Builder builder) {\n        builder.withConnectTimeout(1000 * 5).withReadTimeout(1000 * 5).withRequestTimeout(1000 * 5);\n    }\n\n    @Override\n    public TarantoolCredentials tarantoolCredentials() {\n        return new SimpleTarantoolCredentials(username, password);\n    }\n\n    @Override\n    protected TarantoolServerAddress tarantoolServerAddress() {\n        return new TarantoolServerAddress(host, port);\n    }\n\n    @Override\n    public TarantoolClient\u003cTarantoolTuple, TarantoolResult\u003cTarantoolTuple\u003e\u003e tarantoolClient(\n            TarantoolClientConfig tarantoolClientConfig,\n            TarantoolClusterAddressProvider tarantoolClusterAddressProvider) {\n        return new ProxyTarantoolTupleClient(\n                super.tarantoolClient(tarantoolClientConfig, tarantoolClusterAddressProvider));\n    }\n\n}\n\n```\n\n\n\n\n# License\n\nThe Spring PetClinic sample application is released under version 2.0 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fspring-petclinic-tarantool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftarantool%2Fspring-petclinic-tarantool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fspring-petclinic-tarantool/lists"}