(java || kotlin) && devOps – Telegram
(java || kotlin) && devOps
354 subscribers
7 photos
1 video
7 files
360 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Разработчики AI переизобрели CSV

А теперь серьезно)
Я уже писал, что LLM общаются с помощью JSON и обработка JSON - не то, с чем LLM хорошо работает: https://news.1rj.ru/str/javaKotlinDevOps/484
Поэтому появился TOON.
Почему не YAML или что-то еще?
Данный формат заточен под компактность и удобство обработки LLM. По сути это CSV с метаданными.
Чтобы не быть голословным - пример:

JSON
{ "users": [ { "id": 1, "name": "Alice", "role": "admin" }, { "id": 2, "name": "Bob", "role": "user" } ] }  


TOON
users{id,name,role}: 1,Alice,admin 2,Bob,user


Разница видна невооруженным глазом. Разные тесты показывают выигрыш по размеру на 20-60%, см. https://habr.com/ru/news/966734/
Но есть нюанс - по сути у нас таблица, и максимальная выгода получается на табличных данных. На вложенных структурах - сильно меньше.
Плюс улучшается точность работы модели, но уже не так сильно - процентов на 5.
С другой стороны модели в плане точности ответа уже дошли до такого уровня, когда любые проценты важны.

Другой важный момент - мир AI становится все ближе к обычному ИТ. Примеры:
1) TOON как оптимизированный протокол. Не gRPC, но движение в том же направлении.
2) все актуальнее в связи с нехваткой железа в датацентрах становится кэширование - как в рамках сессии, так и долгосрочное. А это тянет за собой TTL, инвалидацию кэша...
3) structured output - https://news.1rj.ru/str/javaKotlinDevOps/473 - это тоже шаг к традиционным программам
4) RAG как некий аналог БД микросервиса

Что дальше?
Многопоточность? Полноценная БД? Транзакции? Очереди?

#ai #llm
ISR

Или Interface Segregation Principle из SOLID. По большому счёту частный случай Single Responsibility. Легко заметить нарушение - большое число методов с TODO или UnsupportedOperationException в одном классе. Или любое количество, но живущих там в течение долгого времени.

А как его не нарушать?

Самый простой способ - интерфейс из одного метода. Он же SAM - Single Abstraction Method. И лямбдах можно использовать.

Хотя лично мне первое время такие интерфейсы казались какими-то искусственными. Но потом понял - искусственными они становятся когда имеют только одну реализацию. Один на один даёт минус) Тогда бесполезно, да. А вообще - с таких интерфейсов и нужно начинать. И по возможности придерживаться)

#solid
Всем привет!

Я вернулся)

Вернуться заставило вот это видео Егора
Бугаенко https://vkvideo.ru/playlist/3430647_-1001/video-226887147_456239441

Рекомендую. Егор делает то, что у него лучше всего получается) После просмотра возникает один вопрос - так, а что с AI?)

P.S. Ещё из интересного в докладе - linter для shell скриптов https://www.shellcheck.net/# С хорошим online редактором, что важно, т.к. не всегда есть пайплайн, куда можно его включить.
А из грустного - ArchUnit, о котором я хотел написать, все ещё сыроват. Но все равно напишу про него и Spring Modulith.

P.P.S. Надо было в конце доклада майку разыграть. С одной известной надписью-мемом)

#ai #linter #xunit
👍3
Заметка про стандартизацию AI.

Вот и кандидат на стандарт для хранения контекста диалога подъехал https://habr.com/ru/companies/bothub/news/972054 Не факт, что именно эта технология, см. Китай и Гонконг, но сам принцип вполне может стать стандартом.

P.S. Не понимаю, правда, почему его с RAG сравнивают. RAG для хранения доменноспецифичных данных

#ai
(java || kotlin) && devOps
Новая LTS Java. Я о Java 25. Вышла не вчера, поэтому также вышла и хорошая статья с обзором нововведений https://habr.com/ru/companies/T1Holding/articles/946778/. Там даже табличка включения новых фич от 21 до 25 версии есть. И примеры кода - было\стало.…
Небольшой комментарий про quantum resistant фичу в новой Java.

Да, кажется что ещё рано беспокоится.

Но кто мешает задампить что-то зашифрованное сейчас, и взломать при появлении работающего квантового компьютера?

Ещё момент - AI был известен в узких кругах давно, а для широкой публики возник внезапно только при года назад, когда появились более менее работающие LLM

#ai #security
Зачем нужны MCP сервера?

Как я уже говорил: основная проблема AI в разработке - результат работы AI по определению не детерминирован, а код должен решать какую-то определенную задачу с строгой бизнес-логикой.
А что, если не придумывать код на ходу - а это по сути восстановление информации после ее сжатия с потерями - а просто понять, какой именно код нужен и взять готовый пример.
Эту проблемы может решить MCP сервер, в который загрузили официальную документацию с примерами.
Например, https://context7.com/?q=spring
Идея кажется отличной, но есть пару вопросов:
1) по приведенной выше ссылке - какой источник для Spring мне выбрать? Где больше токенов? Или более новый? Или по всем искать? (но это же долго)
2) как понять, что информацию загрузили правильно - всю, без искажений? к какой именно версии относится? у каждого источника есть Benchmark, но как и кто мерит - не ясно
3) кто отвечает за загрузку? предполагается, что это делают авторы библиотек https://context7.com/docs/adding-libraries , но по факту это не так. будет ли загрузка по данному источнику и дальше работать? если это энтузиасты - большой вопрос... одно дело быть автором open-source библиотеки, другое - безымянным автором источника данных в context7

Вообще идея крутая - зачем восстанавливать информацию или парсить интернет, когда можно подсунуть LLM точный источник. Ее бы стандартизировать (что будет с context7 через год? хорошо, если он станет Docker в своей области, а если нет?), популяризовать и прикрутить верификацию авторов - вообще идеально бы было. Сделал jar - добавь source jar, javadoc jar и mcp сервер.

#mcp #ai
null safety в Java - счастье на горизонте?)

Я уже писал про проблему null safety в Java, особенно ярко видимую на фоне Kotlin.
https://news.1rj.ru/str/javaKotlinDevOps/98
В посте по ссылке выше разработчики Kotlin собрали поддерживаемые ими виды аннотаций а-ля @NotNull https://kotlinlang.org/docs/java-interop.html#nullability-annotations
И их число говорит о многом, а точнее о состоянии разброда и шатания в Java мире.

Так вот - похоже в войне Nullable аннотаций наметился победитель, и это JSpecify https://jspecify.dev/docs/user-guide/.
С одной стороны это очередная внешняя библиотека:

dependencies {
implementation 'org.jspecify:jspecify:1.0.0'
....
}


Которую поддерживает IDEA при поиске проблем. Но она и другие библиотеки поддерживает.

Что же изменилось?

А то, что собрался ряд достаточно известных компаний: Google, Oracle, JetBrains, Uber, VMware/Broadcom (а значит и Spring), и они стандартизировали именно JSpecify.
Что это значит, ряд примеров:
1) Spring переводит свой фреймворк на JSpecify к Spring 7\Spring Boot 4
2) про JetBrains и IDEA c Kotlin я уже сказал
3) Google внедряет новые аннотации в Guava. И наверняка куда-то еще)
...

Две основные фишки нового подхода:
1) Uber доработала плагин компилятора NullAway на основе errorprone:

tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableAllChecks = true // Other error prone checks are disabled
option("NullAway:OnlyNullMarked", "true") // Enable nullness checks only in null-marked code
error("NullAway") // bump checks from warnings (default) to errors
option("NullAway:JSpecifyMode", "true") // https://github.com/uber/NullAway/wiki/JSpecify-Support
}
}


и warning в IDEA легким движением руки превращается в error компиляции.
Работает в JDK 17,21 и 22+ https://bugs.openjdk.org/browse/JDK-8225377
Аналогично можно сделать в Maven.

2) Другое важное изменение - возможность задавать значение null safety по умолчанию для пакета, модуля или класса.

@NullMarked
package org.example;

@NullMarked
class MyClass {


Эти объявления означают, что все поля и переменные в классе или пакете должны быть not null.
А если null значение все же нужно - нужно явно пометить его аннотацией @Null

Чтобы заменить старые аннотации на новые есть правила OpenRewrite https://docs.openrewrite.org/recipes/java/jspecify/jspecifybestpractices

Пару ложек дегтя:
1) Oracle в JDK пока аннотации не внедряет. Зато JDK пропатчила начиная с 17-й, чтобы валидация на этапе компиляции заработала.
2) Hibernate присматривается https://github.com/hibernate/hibernate-orm/discussions/6220.

Что я могу сказать в итоге - удачи, дело нужное!

А в прикладе внедрять уже можно. Ситуация НЕ похожа на известную шутку: было 10 разных стандартов, люди решили это изменить и их стало одиннадцать)
Проблема есть, и массовому внедрению ее решения мешал по большому счету тот факт, что единого решения не было.

#null_safety #java
🔥2
Версионирование для REST в Java - оно как бы есть, и его как бы нет)

Основная проблема с версионированием - которую многие, в т.ч. и я, не замечают - следующая.
Версионирование нужно и его почти везде используют, но при этом Spring, который также везде используют, не делает ничего для его поддержки.
Точнее не делал.
Начиная со Spring 7, который уже вышел, данный функционал наконец таки появился: https://habr.com/ru/companies/spring_aio/articles/967454/

Что добавили:
1) определение способа передачи версии:

# Path segment versioning (e.g., /api/v1/users)
spring.mvc.apiversion.use.path-segment=1

# Request header versioning (e.g., X-API-Version: 1.0)
spring.mvc.apiversion.use.header=X-API-Version

# Query parameter versioning (e.g., ?version=1.0)
spring.mvc.apiversion.use.query-parameter=version

# Media type parameter versioning (e.g., Accept: application/json;version=1.0)
spring.mvc.apiversion.use.media-type-parameter[application/json]=version


2) указание версию по умолчанию:

# Basic versioning configuration
spring.mvc.apiversion.default=1.0


3) указание списка поддерживаемых версий и, соответственно, их валидацию:

spring.mvc.apiversion.supported=1.0,2.0


или

# автоматическое детектирование по содержимому контроллеров
spring.mvc.apiversion.detect-supported = true


Естественно, все это можно сделать через код.

4) Стандартный парсер версий - по стандарту семантического версионирования

5) Само собой есть возможность определить нестандартный механизм передачи и формата версий через создание ApiVersionResolver и ApiVersionParser

6) Есть даже ApiVersionDeprecationHandler - стандартизация уведомления клиента об устаревших версиях и автоматическая 400-ка для неподдерживаемых

7) И конечно механизм маршрутизации по версиям, который автоматически разрешает вот такую конструкцию:

public class AccountController {
@GetMapping
public Account getAccount() {
}

@GetMapping(version = "1.1")
public Account getAccount1_1() {
}

@GetMapping(version = "1.2+")
public Account getAccount1_2() {
}

@GetMapping(version = "1.5")
public Account getAccount1_5() {
}
}


8) аналогично для endpoint в функциональном стиле:

RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", version("1.2"),
request -> ServerResponse.ok().body("Hello World")).build();


9) плюс все поддерживается для reactive stack

10) и на клиенте (для тестовых клиентов тоже):

RestClient client = RestClient.builder()
.baseUrl("http://localhost:8080")
.apiVersionInserter(ApiVersionInserter.useHeader("API-Version"))
.build();
...
Account account = client.get().uri("/accounts/1")
.apiVersion(1.1)
.retrieve()
.body(Account.class);


Еще одна важная фича, которая должна была появиться раньше.

#spring #versioning
Зоопарк консолей в Windows.

Пишу на примере Windows 11, но в 10-ке начиная с определенного билда все также.

1) Стандартная консоль Windows - теперь их стало две:
а) это связано с тем, что в Windows появился т.наз Windows Terminal. Это мультиоконная консоль, и при вызове cmd.exe вызывается именно именно. Но т.к. она мультиоконная - там же можно открыть любую другую консоль. Все в IDEA слизали)
б) а старая добрая однооконная консоль вызывается через conhost.exe. Зачем может пригодится старая консоль? Microsoft любят ломать совместимость, и новая консоль к примеру изменила API в плане скрытия окна консоли при старте программы.

2) Powershell - тоже использует Windows Terminal, и их стало даже три:
a) powershell.exe - это старая добрая синяя консоль (цвет значка остался синий, консоль по умолчанию черная), версия 5.х
б) pwsh.exe - новая черная консоль (я снова про цвет значка), версия 7.x. На данный момент ставится только руками. Ключевое замеченное мной отличие - есть API по считыванию данных в консоли, что удобно при работе AI агента, не надо выводить данные в лог.
в) conhost.exe powershell.exe - это способ запустить powershell в старом однооконном режиме

3) Git Bash - очень удобная штука, получаем консоль Linux с большим количеством утилит. Увы, не полный набор, а главное не хватает pacman - менеджера пакетов, который в свою очередь даст поставить все остальное. Еще важная особенность - там используются linux слэши и набор переменных среды хотя и наследуется, но отличается от Windows.

4) если утилит не хватает - можно поставить MSYS. Git Bash = урезанная версия MSYS без pacman + mingw64 (С-шный комполятор gcc в Windows)

5) еще можно поставить WSL - Windows Subsystem for Linux. Поверх нее работает Docker Desktop, а это значит не можно, а нужно ставить). В этом случае появляется еще консоль wsl.exe

В итоге у меня это выглядит как на картинке.
Но в целом получаем рабочую среду)

#win #dev
🤯21
Отличие сеньора от джуна

Смотрел только что видео, где обсуждается, а точнее критикуется "Чистый код" Мартина.
Критика в отдельных местах справедливая, но в большинстве кажется критикой ради критики.
Т.е. из "Чистого кода" пытаются сделать Библию с заповедями, которые якобы нужно понимать буквально. А это очевидно не так.

Но не суть.
Понравилась одна фраза:
В реальных проектах всегда будет "говнокод", т.к. есть сроки. Ключевой момент в том, что сеньор всегда может аргументировано объяснить, почему он написал именно так, а джун - нет)

#memes #dev
👍3
Концепция venv (virtualenv) в Python

Концепция крутая, как по мне ноутбуки и виртуальные окружения - две самые крутые фичи в Python.

Если вкратце о ее сути: ты создаешь для каждого проекта отдельную виртуальную среду, со своим интерпретатором Python (разные версии) и своим набором библиотек.
Это удобно, и решает проблему конфликта зависимостей. Небольшое уточнение: если бы все сервисы и библиотеки явно указывали версии зависимостей - конфликтов бы не было, но мы же говорим о реальном мире.
К слову, концепцию позаимствовала и Java, я про jenv - https://news.1rj.ru/str/javaKotlinDevOps/442 - правда, ограничить можно только версию Java.

Но блин.
Почему утилит, реализующих виртуальные окружения столько:
- venv
- virtualenv
- uv
- Poetry
- PDM
- Hatch
- Rye
- Conda
- Mamba
- Micromamba
- Pixi
- Pipenv
- pyenv-virtualenv
- virtualenvwrapper
- Tox
- Nox
- Devbox
- Flox
- Devenv.sh
- Spack
- Vex
????

Явная иллюстрация анекдота про 10 стандартов)

Ясно, что они не идентичны по функционалу.
1) кто-то добавляет возможность менеджера пакетов (и это правильно),
2) кто-то позволяет формировать список зависимостей проекта requirements.txt (и это тоже правильно),
3) кто-то добавляет возможность делать lock зависимостей (спорная фича IMHO).
Кто-то просто устарел. Кто-то заточен для тестов, где нужна куча разных сред. Кто-то просто добавляет небольшие фишки в другой менеджер, типа убирает необходимость явно включать использование виртуального окружения в консоли (activate).
Но все же...
Причем 4 из них имеют официальный статус)

P.S. Самой крутой и современной считается uv. На данный момент.

#python #java #virtual_env
Про практическое применение AI в разработке

"Низколежащий фрукт" - добавление в проект новых зависимостей.
В IDEA как бы есть для этого auto complete в build и pom файлах. Но работает он... плохо.

Maven. Я вбиваю любую часть имени groupId или artefactId зависимости - и IDE мне ее находит и подставляет всю конструкцию:
<dependency>
...
</dependency>
С плагинами тоже работает, правда есть один момент - у зависимостей и плагинов общий индекс, поэтому чтобы искать плагины нужно вначале вбить groupId. А у всех стандартных плагинов он одинаковый - org.apache.maven.plugins.

Вроде все работает? Нет. Заставить работать индекс по mavenCentral у меня не получилось. А что же работает? Поиск по зависимостям из локального репо. Но его вначале нужно наполнить. Причем эту проблему - поиск по удаленному репозиторию - я помню уже лет 5 минимум.

С Gradle все еще хуже. Тоже ищет только локальные зависимости, но работать нормально не возможно. Для добавления зависимости вначале нужно написать implementation, и начать набирать точное имя зависимости с начала. А плагины не ищет совсем.
Пробовал с Groovy и Kotlin DSL.

Так что тут AI спасет. Но в агентском режиме, не auto complete, т.к. для работы auto complete нужен электрод в мозг, а до этого техника еще не дошла. Т.е. нужен немного другой паттерн работы, но это в целом касается работы с AI.

#maven #gradle
С момента начала массового внедрения (хайпа) LLM прошло года 3. Концепции Machine Learning - лет 70. А вот техника prompt инжиниринга гораздо старше. Одни из первых ее упоминаний встречаются в "1000 и одной ночи") Джин, три желания и все такое)

И надо сказать, что даже на бесплатных тарифах желаний сейчас стало существенно больше) И последствия не такие разрушительные)

#ai
😁4
Еще один AI стандарт намечается.

https://agentskills.io/home

В чем суть?
Есть агенты, каждый агент может делать что-то специфичное. Возникает идея стандартизировать его функционал, чтобы переиспользовать.
Например: чтение pdf файлов, создание xlsx таблиц...
Т.е. в первую очередь это касается навыков небольшого размера, полезных множеству агентов. Но не только.

Если посмотреть что представляет собой спецификация навыка - промты, документация, описания API и скрипты.
Пример: https://github.com/anthropics/skills/tree/main/skills/docx
Т.е. навык нужно поддерживать: версии, обновления, security fixes.

Еще можно сравнить с MCP серверами.
MCP - кастомные навыки, домен-специфичные, требующие доступа к БД или другим сервисам, реализуются в отдельном сервисе (MCP сервер).
Skills - навыки общего назначения, лежат внутри агента.

Почему стандарт? Предложил Anthropic, но поддерживают кроме Claude Code уже OpenAI с Microsoft.
Т.е. как минимум 3 из ведущих dev AI агентов. Глядись и Cline подтянется. И GigaCode.

P.S. Да помню я, что в названии блога Java!))))

#ai #mcp
1
Непрерывное развитие Spring

Когда я увидел вот эту штуку: https://docs.spring.io/spring-framework/reference/core/beans/java/programmatic-bean-registration.html - Programmatic Bean Registration, т.е. новый API для программной регистрации бинов, я подумал: "Стоп, где-то я это уже слышал? А старый API чем не угодил?"
Вот тут человек прям провел расследование с экскурсом в историю https://habr.com/ru/companies/spring_aio/articles/915512/

Ну и как всегда краткое содержание:
а) чуть более удобное API
б) новое API явно предназначено для регистрации бинов, что упрощает работу разного рода оптимизаторов старта приложения. Вот где собака порылась.

Было:

@Component
class ProgrammaticBeanRegistry implements BeanDefinitionRegistryPostProcessor {

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("withCallback",
BeanDefinitionBuilder.genericBeanDefinition(WithCallback.class).setPrimary(true).setLazyInit(false)
.setScope("prototype").getBeanDefinition());
}
}


Стало:

@Component
static class MyBeanRegistry implements BeanRegistrar {

@Override
public void register(BeanRegistry registry, Environment env) {
if (env.matchesProfiles("dev|qa")) {
registry.registerBean("testBean", WithCallback.class, spec -> spec.fallback().lazyInit().order(Ordered.HIGHEST_PRECEDENCE));
} else if (env.matchesProfiles("prod")) {
registry.registerBean("prodBean", WithCallback.class, spec -> spec.primary().prototype().lazyInit());
}
}
}


Эволюцию не остановить)

P.S. Пока все не перепишут код на новое API - оптимизации работать не будут. Как минимум сам Spring это должен сделать.

#spring
1
Немного о инициализации Spring Bean

Я думаю все или почти все Java разработчики знают аннотацию @Primary.
Это когда есть несколько бинов одного типа и корректно указать, какой из них нужно грузить не получается, приходится "прибивать гвоздями".
Корректно - это через имена бинов или привязку к профилям.
Такая необходимость возникает, если проблемный бин в библиотечном коде, и изменить его код невозможно. А автор библиотеки не предусмотрел переопределение бина.

Так вот, для авторов библиотек есть хорошее решение - @Fallback.
Как всегда статья - https://www.baeldung.com/spring-fallback-beans
Как понятно из названия аннотация определяет бин, который загрузится в контекст только тогда, когда других кандидатов не нашлось.
И кроме fallback, ее можно использовать как default. Т.е. если есть ненулевая вероятность того, что бин понадобится переопределить.

Появилась начиная с Spring Framework 6.2.0-M1.

#spring
1
Тест для проверки мозга после из новогодних праздников)

Почему? Тут будут алгоритмы)

Я уже писал про Consistent Ring Hashing - https://news.1rj.ru/str/javaKotlinDevOps/46 - решение для для равномерного распределения данных по нодам\серверам\шардам
Но это не единственное решение, особенно когда появляются требования:
1) много нод (тысячи, десятки тысяч)
2) частое добавление и удаление нод
3) минимальный объем перемещения данных при ребалансировке нод
4) простота алгоритма и, соответственно, его отладки
5) минимальное потребление памяти для хранения данных маршрутизации
6) минимальное время вычисления целевой ноды
7) максимально равномерное распределение данных
8) возможность назначать веса нодам

Естественно, единственно оптимального решения для всех этих требований нет.
Но есть варианты:
1) Consistent Ring Hashing с виртуальными нодами
2) Rendezvous Hashing
3) Jump Consistent Hashing https://iq.opengenus.org/jump-consistent-hash/ (VPN)
4) Consistent Hashing with Bounded Loads https://gweb-research2023-stg.uc.r.appspot.com/blog/consistent-hashing-with-bounded-loads/

И парочка сравнительных статей с которых стоит начать:
https://dtf.ru/gamedev/1215676-consistent-protiv-rendezvous-chem-otlichayutsya-podhody-dlya-heshirovaniya-dannyh-na-servere
https://chaotic.land/ru/posts/2024/09/data-sharding-algorithms/

Для себя сделал такие выводы:
1) Consistent Ring Hashing - база
2) Rendezvous Hashing - простой алгоритм, равномерное распределение "из коробки", но медленнее, что сказывается при большом числе нод
3) Jump Consistent Hashing - быстрый, тоже равномерный "из коробки", но плохо себя ведет при частом изменении числа нод и необходимости добавления весов

P.S. С наступающим!)

#algorithm #distributedsystem
И снова про зависимости)

В продолжение поста проблемы с поиском и добавлением зависимостей в Maven/Gradle проекты https://news.1rj.ru/str/javaKotlinDevOps/503 - в комментарии к посту пришёл @gmyasoedov и предложил решение (разработал если быть точным). Это плагин IDEA, переиспользующий движок и UI поиска зависимостей от Maven Central. Есть итеграция с редактором IDEA и поддержка Maven и Gradle. Плюс некоторые другие плюшки - типа провалиться в папку с локальной зависимостью.

Детали тут https://habr.com/ru/articles/981132/

Лично я буду пробовать.

#idea #maven #gradle
👍1
Третий репозиторий Java разработчика

Изначально был Maven Central.
Потом появился Docker Hub.
Теперь ещё и Hugging Face - хранилище opensource моделей.

И с ним возникают свои нюансы.
Например:
1) у некоторых моделей нужно зайти в UI и запросить доступ, явно согласившись с лицензией. Чем это принципиально отличается от License.me - не понятно

2) если JVM и Docker скрывают от нас железо, то LLM модели - нет. От того, что у вас - CPU, GPU или NPU - зависит выбор модели. Базовая модель может быть одинаковой, но появляется новое измерение - оптимизация под железо. Кроме того, у разных NPU - Intel, AMD, Qualcomm, Google, anyone else?)- разные SDK. А NPU выглядит перспективной штукой, так как он специализированный и более дешёвый. В общем, напрашивается новый слой абстракции.

#ai #llm #infrastructure